diff --git a/src/app/dashboard/dashboard-view.tsx b/src/app/dashboard/dashboard-view.tsx
index 0ea7e47..bf38917 100644
--- a/src/app/dashboard/dashboard-view.tsx
+++ b/src/app/dashboard/dashboard-view.tsx
@@ -1,5 +1,5 @@
'use client';
-
+// src/app/dashboard-view.tsx
import { useState } from "react";
import { styled } from '@mui/material/styles';
import {
@@ -21,6 +21,7 @@ import {
QuickFilter,
QuickFilterControl,
QuickFilterClear,
+ GridEventListener,
} from "@mui/x-data-grid";
import SyncIcon from "@mui/icons-material/Sync";
import RefreshIcon from "@mui/icons-material/Refresh";
@@ -156,6 +157,14 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
}
};
+ // --- NEW: Double Click Handler ---
+ const handleRowDoubleClick: GridEventListener<'rowDoubleClick'> = (params) => {
+ // Only navigate if it's a file. If it's a folder, we could eventually navigate into it.
+ if (!params.row.isFolder) {
+ router.push(`/dashboard/files/${params.id}`);
+ }
+ };
+
const columns: GridColDef[] = [
{
field: "name",
@@ -163,10 +172,12 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
flex: 1.5,
minWidth: 250,
renderCell: (params) => (
-
- {params.row.isFolder ? : }
- {params.value}
-
+
+
+ {params.row.isFolder ? : }
+ {params.value}
+
+
)
},
{
@@ -211,20 +222,20 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
{!isFolder && (
<>
- window.open(`/api/download?id=${params.row.id}&mode=inline`, '_blank')}>
+ { e.stopPropagation(); window.open(`/api/download?id=${params.row.id}&mode=inline`, '_blank'); }}>
- window.location.href = `/api/download?id=${params.row.id}&mode=attachment`}>
+ { e.stopPropagation(); window.location.href = `/api/download?id=${params.row.id}&mode=attachment`; }}>
>
)}
{(isAdmin || isOwner) && (
<>
- router.push(`/update/${params.row.id}`)}>
+ { e.stopPropagation(); router.push(`/update/${params.row.id}`); }}>
- handleDelete(params.row.id, params.row.name)}>
+ { e.stopPropagation(); handleDelete(params.row.id, params.row.name); }}>
>
@@ -273,6 +284,7 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
showToolbar
slots={{ toolbar: CustomToolbar }}
disableRowSelectionOnClick
+ onRowDoubleClick={handleRowDoubleClick} // ADDED THIS HANDLER
initialState={{
columns: {
columnVisibilityModel: {
@@ -280,7 +292,12 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
},
},
}}
- sx={{ border: 'none' }}
+ sx={{
+ border: 'none',
+ '& .MuiDataGrid-row:hover': {
+ cursor: 'pointer',
+ },
+ }}
/>
diff --git a/src/app/dashboard/files/[id]/page.tsx b/src/app/dashboard/files/[id]/page.tsx
new file mode 100644
index 0000000..9a8df92
--- /dev/null
+++ b/src/app/dashboard/files/[id]/page.tsx
@@ -0,0 +1,132 @@
+// src/app/dashboard/files/[id]/page.tsx
+import { getFileNodeById } from "@/data-access/file-nodes";
+import { mapMetadata } from "@/lib/transformers";
+import { notFound } from "next/navigation";
+import {
+ Box, Container, Typography, Paper, Grid, Divider, Stack, Button
+} from "@mui/material";
+import DescriptionIcon from '@mui/icons-material/Description';
+import PhotoIcon from '@mui/icons-material/Photo';
+import ArrowBackIcon from '@mui/icons-material/ArrowBack';
+import Link from "next/link";
+
+export default async function FileDetailPage(props: {
+ params: Promise<{ id: string }>
+}) {
+ // 1. Unwrapping params for Next.js 15/16+
+ const { id } = await props.params;
+
+ // 2. Fetch data via DAL
+ const file = await getFileNodeById(id);
+
+ // 3. Check schema-correct property 'isFolder'
+ if (!file || file.isFolder) {
+ notFound();
+ }
+
+ // 4. Transform data for the UI
+ const data = mapMetadata(file);
+
+ // 5. Derived extension logic
+ const extension = file.name.split('.').pop()?.toLowerCase();
+ const isPDF = extension === 'pdf';
+
+ // 6. Handle BigInt for Size
+ const sizeKB = file.size ? (Number(file.size) / 1024).toFixed(2) : "0";
+
+ return (
+
+ {/* Navigation back to dashboard */}
+
+ }
+ sx={{ mb: 3 }}
+ >
+ Back to Dashboard
+
+
+
+
+
+ {isPDF ? (
+
+ ) : (
+
+ )}
+
+ {file.name}
+
+ ID: {file.id}
+
+
+
+
+
+ {/* Left Column: Core Info & Preview */}
+
+ Summary
+
+
+ {data.textPreview && (
+
+
+ DOCUMENT PREVIEW
+
+
+ "{data.textPreview}..."
+
+
+ )}
+
+
+
+ FILE TYPE
+ {data.type}
+
+
+ SIZE
+ {sizeKB} KB
+
+ {data.pageCount > 0 && (
+
+ PAGE COUNT
+ {data.pageCount}
+
+ )}
+
+ UPLOADED AT
+
+ {new Date(file.createdAt).toLocaleString()}
+
+
+
+
+
+ {/* Right Column: Deep Metadata */}
+
+ Extracted Attributes
+
+
+ {Object.entries(file.metadata as Record || {}).map(([key, value]) => (
+
+
+ {key}
+
+
+ {typeof value === 'object' ? JSON.stringify(value) : String(value)}
+
+
+ ))}
+ {/* FIXED: Properly closed logic for empty metadata */}
+ {Object.keys(file.metadata as object || {}).length === 0 && (
+
+ No extra metadata extracted for this file.
+
+ )}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/lib/text.pdf b/src/lib/text.pdf
deleted file mode 100644
index 9910af6..0000000
Binary files a/src/lib/text.pdf and /dev/null differ
diff --git a/src/lib/transformers.ts b/src/lib/transformers.ts
index 5431867..375846b 100644
--- a/src/lib/transformers.ts
+++ b/src/lib/transformers.ts
@@ -56,4 +56,56 @@ export const mapImageMetadata = (metadata: any) => {
: null
}
};
+};
+
+// src/lib/transformers.ts
+
+/**
+ * Transforms the raw database FileNode into a structured object for the UI.
+ */
+export const mapMetadata = (node: any) => {
+ if (!node) return { title: "Unknown File" };
+
+ const meta = node.metadata || {};
+ const fileName = node.name;
+ const extension = fileName.split('.').pop()?.toLowerCase();
+
+ // --- PDF Logic ---
+ if (extension === 'pdf') {
+ return {
+ fileName,
+ type: 'PDF Document',
+ title: meta.title || fileName,
+ author: meta.author || 'Unknown Author',
+ subject: meta.subject || 'N/A',
+ pageCount: meta.pageCount || 0,
+ keywords: meta.keywords || 'None',
+ textPreview: node.description || meta.textPreview || '', // description field often stores the preview
+ creator: meta.creator || 'N/A',
+ producer: meta.producer || 'N/A'
+ };
+ }
+
+ // --- Image Logic (Sharp/Exif) ---
+ if (['jpg', 'jpeg', 'png', 'webp'].includes(extension || '')) {
+ // Note: your extractor lowercase keys via the sanitizer
+ return {
+ fileName,
+ type: 'Image',
+ device: `${meta.image?.make || ''} ${meta.image?.model || ''}`.trim() || 'Unknown Device',
+ timestamp: meta.photo?.dateTimeOriginal ? new Date(meta.photo.dateTimeOriginal) : null,
+ settings: {
+ aperture: meta.photo?.fNumber ? `f/${meta.photo.fNumber}` : 'N/A',
+ iso: meta.photo?.iSOSpeedRatings || 'N/A',
+ focalLength: meta.photo?.focalLength ? `${meta.photo.focalLength}mm` : 'N/A',
+ }
+ };
+ }
+
+ // --- Default Fallback ---
+ return {
+ fileName,
+ type: extension?.toUpperCase() || 'File',
+ details: meta
+ };
};
\ No newline at end of file