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 */} + + + + + + + {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