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 ```json
{ {
"status": 200, "status": 200,
"message": "Hello from the backend", "message": "Backend data sent successfully",
"folders": [...] "folders": [...]
} }
``` ```
@ -30,6 +30,8 @@
} }
``` ```
- **Required Fields**: cabinet_name, cabinet_sector - **Required Fields**: cabinet_name, cabinet_sector
- **Validation**:
- Parent folder must exist (cabinet_parent_id must be valid)
- **Response Example**: - **Response Example**:
```json ```json
{ {
@ -38,6 +40,25 @@
"folder": {...} "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 ### PATCH /api/dms/folder
- **Description**: Updates an existing folder (rename or move) - **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) // Enhanced hierarchical structure (Cabinet > Drawer > Folder structure)
const documentStructure = ref([ const documentStructure = ref([
{ // {
id: 'cabinet-1', // id: 'cabinet-1',
name: 'JKR Document Cabinet', // name: 'JKR Document Cabinet',
type: 'cabinet', // type: 'cabinet',
path: '/jkr-cabinet', // path: '/jkr-cabinet',
accessLevel: 'department', // accessLevel: 'department',
itemCount: 234, // itemCount: 234,
children: [ // children: [
{ // {
id: 'drawer-1-1', // id: 'drawer-1-1',
name: 'Administrative Drawer', // name: 'Administrative Drawer',
type: 'drawer', // type: 'drawer',
path: '/jkr-cabinet/administrative', // path: '/jkr-cabinet/administrative',
accessLevel: 'department', // accessLevel: 'department',
itemCount: 89, // itemCount: 89,
children: [ // children: [
{ // {
id: 'folder-1-1-1', // id: 'folder-1-1-1',
name: 'Personnel Files', // name: 'Personnel Files',
type: 'folder', // type: 'folder',
path: '/jkr-cabinet/administrative/personnel', // path: '/jkr-cabinet/administrative/personnel',
accessLevel: 'private', // accessLevel: 'private',
itemCount: 45, // itemCount: 45,
children: [] // children: []
}, // },
{ // {
id: 'folder-1-1-2', // id: 'folder-1-1-2',
name: 'Budget Reports', // name: 'Budget Reports',
type: 'folder', // type: 'folder',
path: '/jkr-cabinet/administrative/budget', // path: '/jkr-cabinet/administrative/budget',
accessLevel: 'department', // accessLevel: 'department',
itemCount: 23, // itemCount: 23,
children: [] // children: []
} // }
] // ]
}, // },
{ // {
id: 'drawer-1-2', // id: 'drawer-1-2',
name: 'Project Drawer', // name: 'Project Drawer',
type: 'drawer', // type: 'drawer',
path: '/jkr-cabinet/projects', // path: '/jkr-cabinet/projects',
accessLevel: 'department', // accessLevel: 'department',
itemCount: 145, // itemCount: 145,
children: [ // children: [
{ // {
id: 'folder-1-2-1', // id: 'folder-1-2-1',
name: 'Highway Projects', // name: 'Highway Projects',
type: 'folder', // type: 'folder',
path: '/jkr-cabinet/projects/highway', // path: '/jkr-cabinet/projects/highway',
accessLevel: 'department', // accessLevel: 'department',
itemCount: 67, // itemCount: 67,
children: [] // children: []
}, // },
{ // {
id: 'folder-1-2-2', // id: 'folder-1-2-2',
name: 'Building Projects', // name: 'Building Projects',
type: 'folder', // type: 'folder',
path: '/jkr-cabinet/projects/building', // path: '/jkr-cabinet/projects/building',
accessLevel: 'department', // accessLevel: 'department',
itemCount: 78, // itemCount: 78,
children: [] // children: []
} // }
] // ]
} // }
] // ]
}, // },
{ // {
id: 'cabinet-2', // id: 'cabinet-2',
name: 'Public Documents Cabinet', // name: 'Public Documents Cabinet',
type: 'cabinet', // type: 'cabinet',
path: '/public-cabinet', // path: '/public-cabinet',
accessLevel: 'public', // accessLevel: 'public',
itemCount: 156, // itemCount: 156,
children: [ // children: [
{ // {
id: 'drawer-2-1', // id: 'drawer-2-1',
name: 'Forms & Applications', // name: 'Forms & Applications',
type: 'drawer', // type: 'drawer',
path: '/public-cabinet/forms', // path: '/public-cabinet/forms',
accessLevel: 'public', // accessLevel: 'public',
itemCount: 89, // itemCount: 89,
children: [] // children: []
}, // },
{ // {
id: 'drawer-2-2', // id: 'drawer-2-2',
name: 'Public Announcements', // name: 'Public Announcements',
type: 'drawer', // type: 'drawer',
path: '/public-cabinet/announcements', // path: '/public-cabinet/announcements',
accessLevel: 'public', // accessLevel: 'public',
itemCount: 67, // itemCount: 67,
children: [] // children: []
} // }
] // ]
}, // },
{ // {
id: 'cabinet-3', // id: 'cabinet-3',
name: 'Personal Documents', // name: 'Personal Documents',
type: 'cabinet', // type: 'cabinet',
path: '/personal-cabinet', // path: '/personal-cabinet',
accessLevel: 'personal', // accessLevel: 'personal',
itemCount: 45, // itemCount: 45,
children: [ // children: [
{ // {
id: 'drawer-3-1', // id: 'drawer-3-1',
name: 'My Documents', // name: 'My Documents',
type: 'drawer', // type: 'drawer',
path: '/personal-cabinet/my-docs', // path: '/personal-cabinet/my-docs',
accessLevel: 'personal', // accessLevel: 'personal',
itemCount: 25, // itemCount: 25,
children: [] // children: []
}, // },
{ // {
id: 'drawer-3-2', // id: 'drawer-3-2',
name: 'Private Files', // name: 'Private Files',
type: 'drawer', // type: 'drawer',
path: '/personal-cabinet/private', // path: '/personal-cabinet/private',
accessLevel: 'private', // accessLevel: 'private',
itemCount: 20, // itemCount: 20,
children: [] // children: []
} // }
] // ]
} // }
]); ]);
// Current folder contents (what's displayed in the main view) // Current folder contents (what's displayed in the main view)
@ -322,25 +322,85 @@ const getFileTypeColor = (fileName) => {
return colorMap[extension] || colorMap.default; return colorMap[extension] || colorMap.default;
}; };
const mapAPIToDocumentStructure = async () => { // Function to build path from parent_id chain
const response = await fetch('/api/dms/folder'); const buildPathFromParentId = (item, allItems) => {
const APIData = await response.json(); const pathSegments = [];
let currentItem = item;
// const documentStructure.value = APIData.map(obj => { // Traverse up the parent chain until we reach a root item (null parent_id)
// id: obj.cb_id, while (currentItem) {
// name: obj.cb_name, // Add the current item's name to the start of the path
// type: obj.cb_type || "folder", pathSegments.unshift(currentItem.name.toLowerCase().replace(/\s+/g, '-'));
// parent_id: obj.cb_parent_id,
// access_level: obj.cb_access_level || "public",
// itemCount: null,
// children: obj.children_count || null
// })
return documentStructure.value; // 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 () => {
try {
const response = await fetch('/api/dms/folder');
const dbRecords = await response.json();
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 // Navigation functions
const buildBreadcrumbs = (path) => { const buildBreadcrumbs = async (path) => {
if (path === '/') { if (path === '/') {
return [{ name: 'Root', path: '/', type: 'root' }]; return [{ name: 'Root', path: '/', type: 'root' }];
} }
@ -348,8 +408,10 @@ const buildBreadcrumbs = (path) => {
const crumbs = [{ name: 'Root', path: '/', type: 'root' }]; const crumbs = [{ name: 'Root', path: '/', type: 'root' }];
const segments = path.split('/').filter(Boolean); const segments = path.split('/').filter(Boolean);
// const documentStructure.value = await mapAPIToDocumentStructure(); documentStructure.value = await mapAPIToDocumentStructure();
console.log(documentStructure.value);
let itemsToSearch = documentStructure.value; let itemsToSearch = documentStructure.value;
console.log(itemsToSearch);
let currentPath = ''; let currentPath = '';
for (const segment of segments) { for (const segment of segments) {
@ -456,7 +518,7 @@ const loadFolderContents = async (path) => {
try { try {
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise(resolve => setTimeout(resolve, 200));
const item = findItemByPath(path); const item = findItemByPath(path, documentStructure.value);
if (item && item.children) { if (item && item.children) {
currentFolderContents.value = item.children.map(child => ({ currentFolderContents.value = item.children.map(child => ({
...child, ...child,
@ -1031,7 +1093,7 @@ const getTabIconClasses = (tab) => {
}; };
// Lifecycle hooks // Lifecycle hooks
onMounted(() => { onMounted(async () => {
checkMobileView(); checkMobileView();
// Add event listeners // Add event listeners
@ -1040,6 +1102,9 @@ onMounted(() => {
document.addEventListener('click', handleGlobalClick); document.addEventListener('click', handleGlobalClick);
document.addEventListener('contextmenu', handleGlobalContextMenu); document.addEventListener('contextmenu', handleGlobalContextMenu);
// Populate document structure
await mapAPIToDocumentStructure();
// Initialize navigation // Initialize navigation
navigateToPath('/', false); navigateToPath('/', false);

View File

@ -3,9 +3,9 @@ import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient(); const prisma = new PrismaClient();
export default defineEventHandler( async (event) => { 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(); const folders = await prisma.cabinets.findMany();

View File

@ -3,7 +3,7 @@ import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient(); const prisma = new PrismaClient();
export default defineEventHandler( async (event) => { 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"; // 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. // Checked body data.
const folderData = { const folderData = {
cb_name: body.cabinet_name, cb_name: body.cabinet_name,