generated from corrad-software/corrad-af-2024

Create folder function now properly works to send data to the backend and is reflected in the database. The frontend still displays hierarchy from dummy data.
826 lines
30 KiB
Vue
826 lines
30 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);
|
|
};
|
|
|
|
// Convert flat array to tree structure
|
|
const convertToTreeStructure = (folders) => {
|
|
const folderMap = new Map();
|
|
const tree = [];
|
|
|
|
// First pass: create folder objects and store in map
|
|
folders.forEach(folder => {
|
|
folderMap.set(folder.cb_id, {
|
|
id: folder.cb_id,
|
|
name: folder.cb_name,
|
|
path: '', // We'll build this in the second pass
|
|
type: 'folder',
|
|
hasChildren: false,
|
|
children: [],
|
|
lastModified: folder.modified_at,
|
|
itemCount: 0,
|
|
icon: 'mdi:folder',
|
|
// Store original data for API operations
|
|
originalData: {
|
|
cb_parent_id: folder.cb_parent_id,
|
|
cb_sector: folder.cb_sector,
|
|
cb_owner: folder.cb_owner,
|
|
dp_id: folder.dp_id,
|
|
userID: folder.userID
|
|
}
|
|
});
|
|
});
|
|
|
|
// Second pass: build hierarchy and paths
|
|
folders.forEach(folder => {
|
|
const folderNode = folderMap.get(folder.cb_id);
|
|
|
|
if (folder.cb_parent_id === null) {
|
|
// Root level folder
|
|
folderNode.path = `/${folder.cb_name}`;
|
|
tree.push(folderNode);
|
|
} else {
|
|
// Has a parent
|
|
const parentNode = folderMap.get(folder.cb_parent_id);
|
|
if (parentNode) {
|
|
parentNode.hasChildren = true;
|
|
parentNode.children.push(folderNode);
|
|
// Build path by traversing up the tree
|
|
let path = `/${folder.cb_name}`;
|
|
let currentParent = parentNode;
|
|
while (currentParent) {
|
|
path = `/${currentParent.name}${path}`;
|
|
currentParent = currentParent.originalData.cb_parent_id ?
|
|
folderMap.get(currentParent.originalData.cb_parent_id) :
|
|
null;
|
|
}
|
|
folderNode.path = path;
|
|
parentNode.itemCount += 1;
|
|
} else {
|
|
// If parent not found, add to root level
|
|
folderNode.path = `/${folder.cb_name}`;
|
|
tree.push(folderNode);
|
|
}
|
|
}
|
|
});
|
|
|
|
return tree;
|
|
};
|
|
|
|
// Load all folders from API
|
|
const loadAllFolders = async () => {
|
|
try {
|
|
const response = await fetch('/api/dms/folder');
|
|
const data = await response.json();
|
|
|
|
if (data.status === 200) {
|
|
// Convert flat array to tree structure
|
|
mockFolderStructure.value = convertToTreeStructure(data.folders);
|
|
} else {
|
|
throw new Error(data.message);
|
|
}
|
|
} catch (err) {
|
|
error('Failed to load folder structure: ' + err.message);
|
|
}
|
|
};
|
|
|
|
// Modified loadFolderContents to work with the tree structure
|
|
const loadFolderContents = async (path) => {
|
|
isLoading.value = true;
|
|
|
|
try {
|
|
// Find the current folder in the tree structure
|
|
let currentContents = [];
|
|
const pathParts = path.split('/').filter(Boolean);
|
|
|
|
if (path === '/') {
|
|
// Root level - show top-level folders
|
|
currentContents = mockFolderStructure.value.map(folder => ({
|
|
id: folder.id,
|
|
name: folder.name,
|
|
type: 'folder',
|
|
itemCount: folder.itemCount,
|
|
lastModified: folder.lastModified,
|
|
icon: folder.icon
|
|
}));
|
|
} else {
|
|
// Navigate to the correct folder in the tree
|
|
let currentNode = null;
|
|
let currentLevel = mockFolderStructure.value;
|
|
|
|
for (const part of pathParts) {
|
|
currentNode = currentLevel.find(f => f.name === part);
|
|
if (!currentNode) break;
|
|
currentLevel = currentNode.children;
|
|
}
|
|
|
|
if (currentNode) {
|
|
currentContents = currentNode.children.map(child => ({
|
|
id: child.id,
|
|
name: child.name,
|
|
type: 'folder',
|
|
itemCount: child.itemCount,
|
|
lastModified: child.lastModified,
|
|
icon: child.icon
|
|
}));
|
|
}
|
|
}
|
|
|
|
folderContents.value = currentContents;
|
|
} 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 {
|
|
// Only proceed if creating a folder/subfolder
|
|
if (creationType.value !== 'folder' && creationType.value !== 'subfolder') {
|
|
return;
|
|
}
|
|
|
|
const folderData = {
|
|
cabinet_name: itemName.value.trim(),
|
|
cabinet_parent_id: selectedPath.value === '/' ? null :
|
|
currentFolder.value?.id || null,
|
|
cabinet_owner: null,
|
|
cabinet_sector: "ayam", // Using the same sector as example
|
|
dp_id: null,
|
|
userID: null
|
|
};
|
|
|
|
// Make API call to create folder
|
|
const response = await fetch('/api/dms/folder', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(folderData)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.status === 201) {
|
|
// Reload the entire folder structure to get the updated data
|
|
await loadAllFolders();
|
|
success(`Successfully created folder: ${folderData.cabinet_name}`);
|
|
emit('create-complete', result.folder);
|
|
closeModal();
|
|
} else {
|
|
throw new Error(result.message);
|
|
}
|
|
} catch (err) {
|
|
error('Failed to create folder: ' + 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(async () => {
|
|
await loadAllFolders();
|
|
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> |