Linked navigation pane to API

The navigation pane now matches the document structure from database, but breadcrumbs bar became broken, not yet fixed.

Added check to folder creation so that subfolders cannot be created in parent folders that don't exist.
This commit is contained in:
shb 2025-06-19 11:24:19 +08:00
parent 384d571997
commit 274b1cf693
4 changed files with 243 additions and 144 deletions

View File

@ -10,7 +10,7 @@
```json
{
"status": 200,
"message": "Hello from the backend",
"message": "Backend data sent successfully",
"folders": [...]
}
```
@ -30,6 +30,8 @@
}
```
- **Required Fields**: cabinet_name, cabinet_sector
- **Validation**:
- Parent folder must exist (cabinet_parent_id must be valid)
- **Response Example**:
```json
{
@ -38,6 +40,25 @@
"folder": {...}
}
```
- **Error Responses**:
```json
{
"status": 400,
"message": "cabinet_name and cabinet_sector are required"
}
```
```json
{
"status": 400,
"message": "Parent folder does not exist"
}
```
```json
{
"status": 400,
"message": "Body was not received"
}
```
### PATCH /api/dms/folder
- **Description**: Updates an existing folder (rename or move)

View File

@ -100,128 +100,128 @@ const dropTarget = ref(null);
// Enhanced hierarchical structure (Cabinet > Drawer > Folder structure)
const documentStructure = ref([
{
id: 'cabinet-1',
name: 'JKR Document Cabinet',
type: 'cabinet',
path: '/jkr-cabinet',
accessLevel: 'department',
itemCount: 234,
children: [
{
id: 'drawer-1-1',
name: 'Administrative Drawer',
type: 'drawer',
path: '/jkr-cabinet/administrative',
accessLevel: 'department',
itemCount: 89,
children: [
{
id: 'folder-1-1-1',
name: 'Personnel Files',
type: 'folder',
path: '/jkr-cabinet/administrative/personnel',
accessLevel: 'private',
itemCount: 45,
children: []
},
{
id: 'folder-1-1-2',
name: 'Budget Reports',
type: 'folder',
path: '/jkr-cabinet/administrative/budget',
accessLevel: 'department',
itemCount: 23,
children: []
}
]
},
{
id: 'drawer-1-2',
name: 'Project Drawer',
type: 'drawer',
path: '/jkr-cabinet/projects',
accessLevel: 'department',
itemCount: 145,
children: [
{
id: 'folder-1-2-1',
name: 'Highway Projects',
type: 'folder',
path: '/jkr-cabinet/projects/highway',
accessLevel: 'department',
itemCount: 67,
children: []
},
{
id: 'folder-1-2-2',
name: 'Building Projects',
type: 'folder',
path: '/jkr-cabinet/projects/building',
accessLevel: 'department',
itemCount: 78,
children: []
}
]
}
]
},
{
id: 'cabinet-2',
name: 'Public Documents Cabinet',
type: 'cabinet',
path: '/public-cabinet',
accessLevel: 'public',
itemCount: 156,
children: [
{
id: 'drawer-2-1',
name: 'Forms & Applications',
type: 'drawer',
path: '/public-cabinet/forms',
accessLevel: 'public',
itemCount: 89,
children: []
},
{
id: 'drawer-2-2',
name: 'Public Announcements',
type: 'drawer',
path: '/public-cabinet/announcements',
accessLevel: 'public',
itemCount: 67,
children: []
}
]
},
{
id: 'cabinet-3',
name: 'Personal Documents',
type: 'cabinet',
path: '/personal-cabinet',
accessLevel: 'personal',
itemCount: 45,
children: [
{
id: 'drawer-3-1',
name: 'My Documents',
type: 'drawer',
path: '/personal-cabinet/my-docs',
accessLevel: 'personal',
itemCount: 25,
children: []
},
{
id: 'drawer-3-2',
name: 'Private Files',
type: 'drawer',
path: '/personal-cabinet/private',
accessLevel: 'private',
itemCount: 20,
children: []
}
]
}
// {
// id: 'cabinet-1',
// name: 'JKR Document Cabinet',
// type: 'cabinet',
// path: '/jkr-cabinet',
// accessLevel: 'department',
// itemCount: 234,
// children: [
// {
// id: 'drawer-1-1',
// name: 'Administrative Drawer',
// type: 'drawer',
// path: '/jkr-cabinet/administrative',
// accessLevel: 'department',
// itemCount: 89,
// children: [
// {
// id: 'folder-1-1-1',
// name: 'Personnel Files',
// type: 'folder',
// path: '/jkr-cabinet/administrative/personnel',
// accessLevel: 'private',
// itemCount: 45,
// children: []
// },
// {
// id: 'folder-1-1-2',
// name: 'Budget Reports',
// type: 'folder',
// path: '/jkr-cabinet/administrative/budget',
// accessLevel: 'department',
// itemCount: 23,
// children: []
// }
// ]
// },
// {
// id: 'drawer-1-2',
// name: 'Project Drawer',
// type: 'drawer',
// path: '/jkr-cabinet/projects',
// accessLevel: 'department',
// itemCount: 145,
// children: [
// {
// id: 'folder-1-2-1',
// name: 'Highway Projects',
// type: 'folder',
// path: '/jkr-cabinet/projects/highway',
// accessLevel: 'department',
// itemCount: 67,
// children: []
// },
// {
// id: 'folder-1-2-2',
// name: 'Building Projects',
// type: 'folder',
// path: '/jkr-cabinet/projects/building',
// accessLevel: 'department',
// itemCount: 78,
// children: []
// }
// ]
// }
// ]
// },
// {
// id: 'cabinet-2',
// name: 'Public Documents Cabinet',
// type: 'cabinet',
// path: '/public-cabinet',
// accessLevel: 'public',
// itemCount: 156,
// children: [
// {
// id: 'drawer-2-1',
// name: 'Forms & Applications',
// type: 'drawer',
// path: '/public-cabinet/forms',
// accessLevel: 'public',
// itemCount: 89,
// children: []
// },
// {
// id: 'drawer-2-2',
// name: 'Public Announcements',
// type: 'drawer',
// path: '/public-cabinet/announcements',
// accessLevel: 'public',
// itemCount: 67,
// children: []
// }
// ]
// },
// {
// id: 'cabinet-3',
// name: 'Personal Documents',
// type: 'cabinet',
// path: '/personal-cabinet',
// accessLevel: 'personal',
// itemCount: 45,
// children: [
// {
// id: 'drawer-3-1',
// name: 'My Documents',
// type: 'drawer',
// path: '/personal-cabinet/my-docs',
// accessLevel: 'personal',
// itemCount: 25,
// children: []
// },
// {
// id: 'drawer-3-2',
// name: 'Private Files',
// type: 'drawer',
// path: '/personal-cabinet/private',
// accessLevel: 'private',
// itemCount: 20,
// children: []
// }
// ]
// }
]);
// Current folder contents (what's displayed in the main view)
@ -322,25 +322,85 @@ const getFileTypeColor = (fileName) => {
return colorMap[extension] || colorMap.default;
};
// Function to build path from parent_id chain
const buildPathFromParentId = (item, allItems) => {
const pathSegments = [];
let currentItem = item;
// Traverse up the parent chain until we reach a root item (null parent_id)
while (currentItem) {
// Add the current item's name to the start of the path
pathSegments.unshift(currentItem.name.toLowerCase().replace(/\s+/g, '-'));
// If we've reached a root item (null parent_id), break the loop
if (!currentItem.parent_id) break;
// Find the parent item
currentItem = allItems.find(i => i.id === currentItem.parent_id);
// Break if we can't find the parent (shouldn't happen in a valid tree)
if (!currentItem) break;
}
// Construct the final path with leading slash
return '/' + pathSegments.join('/');
};
// Function to convert database records to frontend structure
const convertDbToFrontendStructure = (dbRecords) => {
// First pass: Create a map of all items with their paths
const itemsWithPaths = dbRecords.folders.map(record => ({
id: record.cb_id,
name: record.cb_name,
type: record.cb_type || 'folder',
parent_id: record.cb_parent_id,
accessLevel: record.cb_access_level || 'public',
itemCount: record.item_count || 0,
children: [],
path: '' // Will be populated in the next step
}));
// Second pass: Build paths for all items
itemsWithPaths.forEach(item => {
item.path = buildPathFromParentId(item, itemsWithPaths);
});
// Third pass: Build the tree structure
const rootItems = itemsWithPaths.filter(item => !item.parent_id);
// Recursive function to build the tree
const buildTree = (items) => {
return items.map(item => {
const children = itemsWithPaths.filter(child => child.parent_id === item.id);
return {
...item,
children: children.length > 0 ? buildTree(children) : []
};
});
};
return buildTree(rootItems);
};
const mapAPIToDocumentStructure = async () => {
const response = await fetch('/api/dms/folder');
const APIData = await response.json();
try {
const response = await fetch('/api/dms/folder');
const dbRecords = await response.json();
// const documentStructure.value = APIData.map(obj => {
// id: obj.cb_id,
// name: obj.cb_name,
// type: obj.cb_type || "folder",
// parent_id: obj.cb_parent_id,
// access_level: obj.cb_access_level || "public",
// itemCount: null,
// children: obj.children_count || null
// })
return documentStructure.value;
}
console.log(dbRecords);
// Convert database records to frontend structure
documentStructure.value = convertDbToFrontendStructure(dbRecords);
return documentStructure.value;
} catch (error) {
console.error('Error mapping API data:', error);
return [];
}
};
// Navigation functions
const buildBreadcrumbs = (path) => {
const buildBreadcrumbs = async (path) => {
if (path === '/') {
return [{ name: 'Root', path: '/', type: 'root' }];
}
@ -348,8 +408,10 @@ const buildBreadcrumbs = (path) => {
const crumbs = [{ name: 'Root', path: '/', type: 'root' }];
const segments = path.split('/').filter(Boolean);
// const documentStructure.value = await mapAPIToDocumentStructure();
documentStructure.value = await mapAPIToDocumentStructure();
console.log(documentStructure.value);
let itemsToSearch = documentStructure.value;
console.log(itemsToSearch);
let currentPath = '';
for (const segment of segments) {
@ -456,7 +518,7 @@ const loadFolderContents = async (path) => {
try {
await new Promise(resolve => setTimeout(resolve, 200));
const item = findItemByPath(path);
const item = findItemByPath(path, documentStructure.value);
if (item && item.children) {
currentFolderContents.value = item.children.map(child => ({
...child,
@ -1031,7 +1093,7 @@ const getTabIconClasses = (tab) => {
};
// Lifecycle hooks
onMounted(() => {
onMounted(async () => {
checkMobileView();
// Add event listeners
@ -1039,6 +1101,9 @@ onMounted(() => {
window.addEventListener('keydown', handleKeyboardShortcuts);
document.addEventListener('click', handleGlobalClick);
document.addEventListener('contextmenu', handleGlobalContextMenu);
// Populate document structure
await mapAPIToDocumentStructure();
// Initialize navigation
navigateToPath('/', false);

View File

@ -3,9 +3,9 @@ import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default defineEventHandler( async (event) => {
console.log("This is a test for a GET request to the backend");
console.log("GET request sent received from backend.");
const successMsg = "Hello from the backend";
const successMsg = "Backend data sent successfully";
const folders = await prisma.cabinets.findMany();

View File

@ -3,7 +3,7 @@ import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default defineEventHandler( async (event) => {
console.log("This is a test for a POST request to the backend");
console.log("POST request received by backend.");
// const successMsg = "Hello from the backend";
@ -39,6 +39,19 @@ export default defineEventHandler( async (event) => {
};
};
const parentExists = await prisma.cabinets.findUnique({
where: {
cb_id: body.cabinet_parent_id
}
});
if (!parentExists) {
return {
status: 400,
message: "Parent folder does not exist"
}
}
// Checked body data.
const folderData = {
cb_name: body.cabinet_name,