2026-01-08 05:41:31 +00:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useState } from "react";
|
2026-01-11 13:41:54 +00:00
|
|
|
import {
|
|
|
|
|
Button, CircularProgress, Box, Chip, IconButton, Tooltip, Typography
|
|
|
|
|
} from "@mui/material";
|
2026-01-08 05:41:31 +00:00
|
|
|
import SyncIcon from "@mui/icons-material/Sync";
|
2026-01-11 13:41:54 +00:00
|
|
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
|
|
|
|
import FolderIcon from "@mui/icons-material/Folder";
|
|
|
|
|
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
|
|
|
|
import DeleteIcon from "@mui/icons-material/Delete";
|
2026-01-08 05:41:31 +00:00
|
|
|
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
|
|
|
|
import { syncOneDrive } from "./sync-actions";
|
2026-01-11 13:41:54 +00:00
|
|
|
import { deleteFileAction } from "./actions"; // Un-commented and assumed active
|
2026-01-08 05:41:31 +00:00
|
|
|
import { useRouter } from "next/navigation";
|
|
|
|
|
|
|
|
|
|
interface DashboardViewProps {
|
|
|
|
|
initialFiles: any[];
|
2026-01-11 13:41:54 +00:00
|
|
|
user?: {
|
|
|
|
|
id?: string;
|
|
|
|
|
role?: string;
|
|
|
|
|
};
|
2026-01-08 05:41:31 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
export default function DashboardView({ initialFiles, user }: DashboardViewProps) {
|
2026-01-08 05:41:31 +00:00
|
|
|
const [loading, setLoading] = useState(false);
|
2026-01-11 13:41:54 +00:00
|
|
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
2026-01-08 05:41:31 +00:00
|
|
|
const router = useRouter();
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
const isAdmin = user?.role === "ADMIN";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Recursive function to build the virtual path for the "Location" column.
|
|
|
|
|
* Ensures we see "Projects / Project-1" instead of UUIDs or "ROOT".
|
|
|
|
|
*/
|
|
|
|
|
const getVirtualPath = (parentId: string | null): string => {
|
|
|
|
|
if (!parentId) return "WebCalibre";
|
|
|
|
|
const parent = initialFiles.find((f) => f.id === parentId);
|
|
|
|
|
if (!parent) return "WebCalibre";
|
|
|
|
|
|
|
|
|
|
// Recursive lookup for breadcrumbs
|
|
|
|
|
const prefix = parent.parentId ? `${getVirtualPath(parent.parentId)} / ` : "";
|
|
|
|
|
return `${prefix}${parent.name}`;
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-08 05:41:31 +00:00
|
|
|
const handleSync = async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
await syncOneDrive();
|
|
|
|
|
router.refresh();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Sync failed:", error);
|
|
|
|
|
alert("Failed to sync OneDrive");
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
const handleRefresh = () => {
|
|
|
|
|
setIsRefreshing(true);
|
|
|
|
|
router.refresh();
|
|
|
|
|
// Visual feedback for the refresh action
|
|
|
|
|
setTimeout(() => setIsRefreshing(false), 800);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDelete = async (id: string, name: string) => {
|
|
|
|
|
if (!confirm(`Are you sure you want to delete "${name}"?`)) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await deleteFileAction(id);
|
|
|
|
|
router.refresh();
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
alert(error.message || "Failed to delete file");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-08 05:41:31 +00:00
|
|
|
const columns: GridColDef[] = [
|
|
|
|
|
{
|
2026-01-11 13:41:54 +00:00
|
|
|
field: "name",
|
|
|
|
|
headerName: "Name",
|
|
|
|
|
flex: 1.5,
|
|
|
|
|
minWidth: 250,
|
|
|
|
|
renderCell: (params) => (
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
|
|
|
{params.row.isFolder ? (
|
|
|
|
|
<FolderIcon sx={{ color: '#FFB020' }} /> // Folder yellow
|
|
|
|
|
) : (
|
|
|
|
|
<InsertDriveFileIcon color="action" />
|
|
|
|
|
)}
|
|
|
|
|
<Typography variant="body2" sx={{ fontWeight: params.row.isFolder ? 600 : 400 }}>
|
|
|
|
|
{params.value}
|
|
|
|
|
</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: "parentId",
|
|
|
|
|
headerName: "Location (Project)",
|
|
|
|
|
flex: 1,
|
|
|
|
|
minWidth: 200,
|
|
|
|
|
renderCell: (params) => {
|
|
|
|
|
const path = getVirtualPath(params.value);
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip title={path}>
|
|
|
|
|
<Chip
|
|
|
|
|
label={path}
|
|
|
|
|
size="small"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
color={path === "WebCalibre" ? "default" : "primary"}
|
|
|
|
|
sx={{ maxWidth: '100%' }}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: "type",
|
2026-01-08 05:41:31 +00:00
|
|
|
headerName: "Type",
|
2026-01-11 13:41:54 +00:00
|
|
|
width: 100,
|
|
|
|
|
valueGetter: (value, row) => {
|
|
|
|
|
// Priority: Metadata -> isFolder check -> fallback
|
|
|
|
|
const type = row.metadata?.type || (row.isFolder ? "FOLDER" : "FILE");
|
|
|
|
|
// Ensure we don't display a UUID here if the sync was messy
|
|
|
|
|
return type.length > 10 && row.isFolder ? "FOLDER" : type;
|
|
|
|
|
},
|
|
|
|
|
renderCell: (params) => (
|
|
|
|
|
<Typography variant="caption" sx={{ fontWeight: 'bold', color: 'text.secondary' }}>
|
|
|
|
|
{params.value}
|
|
|
|
|
</Typography>
|
|
|
|
|
)
|
2026-01-08 05:41:31 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: "size",
|
2026-01-11 13:41:54 +00:00
|
|
|
headerName: "Size",
|
2026-01-08 05:41:31 +00:00
|
|
|
width: 120,
|
2026-01-11 13:41:54 +00:00
|
|
|
valueGetter: (value, row) => {
|
|
|
|
|
if (row.isFolder) return null;
|
|
|
|
|
return value;
|
|
|
|
|
},
|
|
|
|
|
renderCell: (params) => {
|
|
|
|
|
if (params.value === null) return "--";
|
|
|
|
|
const mb = (Number(params.value) / 1024 / 1024).toFixed(2);
|
|
|
|
|
return `${mb} MB`;
|
|
|
|
|
}
|
2026-01-08 05:41:31 +00:00
|
|
|
},
|
2026-01-11 13:41:54 +00:00
|
|
|
{
|
|
|
|
|
field: "actions",
|
|
|
|
|
headerName: "Actions",
|
|
|
|
|
width: 100,
|
|
|
|
|
sortable: false,
|
|
|
|
|
align: 'right',
|
|
|
|
|
headerAlign: 'right',
|
|
|
|
|
renderCell: (params) => {
|
|
|
|
|
const isOwner = params.row.ownerId === user?.id;
|
|
|
|
|
if (isAdmin || isOwner) {
|
|
|
|
|
return (
|
|
|
|
|
<IconButton
|
|
|
|
|
size="small"
|
|
|
|
|
color="error"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
handleDelete(params.row.id, params.row.name);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<DeleteIcon fontSize="small" />
|
|
|
|
|
</IconButton>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-08 05:41:31 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
2026-01-11 13:41:54 +00:00
|
|
|
<Box className="space-y-4">
|
|
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mb: 2 }}>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outlined"
|
|
|
|
|
startIcon={isRefreshing ? <CircularProgress size={16} /> : <RefreshIcon />}
|
|
|
|
|
onClick={handleRefresh}
|
|
|
|
|
disabled={isRefreshing}
|
|
|
|
|
>
|
|
|
|
|
{isRefreshing ? "Refreshing..." : "Refresh List"}
|
|
|
|
|
</Button>
|
|
|
|
|
|
2026-01-08 05:41:31 +00:00
|
|
|
<Button
|
|
|
|
|
variant="contained"
|
|
|
|
|
startIcon={loading ? <CircularProgress size={20} color="inherit" /> : <SyncIcon />}
|
|
|
|
|
onClick={handleSync}
|
|
|
|
|
disabled={loading}
|
|
|
|
|
>
|
|
|
|
|
{loading ? "Syncing..." : "Sync OneDrive"}
|
|
|
|
|
</Button>
|
2026-01-11 13:41:54 +00:00
|
|
|
</Box>
|
2026-01-08 05:41:31 +00:00
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
<Box sx={{
|
|
|
|
|
height: 700,
|
|
|
|
|
width: "100%",
|
|
|
|
|
bgcolor: 'background.paper',
|
|
|
|
|
borderRadius: 3,
|
|
|
|
|
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)',
|
|
|
|
|
overflow: 'hidden'
|
|
|
|
|
}}>
|
2026-01-08 05:41:31 +00:00
|
|
|
<DataGrid
|
|
|
|
|
rows={initialFiles}
|
|
|
|
|
columns={columns}
|
|
|
|
|
pageSizeOptions={[10, 25, 50]}
|
|
|
|
|
initialState={{
|
|
|
|
|
pagination: { paginationModel: { pageSize: 10 } },
|
2026-01-11 13:41:54 +00:00
|
|
|
sorting: {
|
|
|
|
|
sortModel: [{ field: 'name', sort: 'asc' }],
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
disableRowSelectionOnClick
|
|
|
|
|
sx={{
|
|
|
|
|
border: 'none',
|
|
|
|
|
'& .MuiDataGrid-columnHeaders': {
|
|
|
|
|
bgcolor: '#f8f9fa',
|
|
|
|
|
borderBottom: '1px solid #eee',
|
|
|
|
|
},
|
|
|
|
|
'& .MuiDataGrid-cell': {
|
|
|
|
|
borderBottom: '1px solid #f0f0f0',
|
|
|
|
|
},
|
2026-01-08 05:41:31 +00:00
|
|
|
}}
|
|
|
|
|
/>
|
2026-01-11 13:41:54 +00:00
|
|
|
</Box>
|
|
|
|
|
</Box>
|
2026-01-08 05:41:31 +00:00
|
|
|
);
|
|
|
|
|
}
|