2026-01-08 05:41:31 +00:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useState } from "react";
|
2026-01-09 06:58:44 +00:00
|
|
|
import {
|
|
|
|
|
Box, Button, Typography, Paper, LinearProgress, Stack,
|
2026-01-11 13:41:54 +00:00
|
|
|
TextField, MenuItem, IconButton, Tooltip, Divider,
|
|
|
|
|
InputAdornment
|
2026-01-09 06:58:44 +00:00
|
|
|
} from "@mui/material";
|
2026-01-08 05:41:31 +00:00
|
|
|
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
2026-01-09 06:58:44 +00:00
|
|
|
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
|
2026-01-11 13:41:54 +00:00
|
|
|
import FolderIcon from "@mui/icons-material/Folder";
|
|
|
|
|
import AssignmentIcon from '@mui/icons-material/Assignment';
|
|
|
|
|
import { useRouter } from "next/navigation";
|
2026-01-09 06:58:44 +00:00
|
|
|
import { uploadFileAction, createFolderAction } from "./_actions";
|
2026-01-08 05:41:31 +00:00
|
|
|
|
|
|
|
|
/**
|
2026-01-09 06:58:44 +00:00
|
|
|
* Format bytes to human readable string (MiB/KiB)
|
2026-01-08 05:41:31 +00:00
|
|
|
*/
|
|
|
|
|
const formatFileSize = (bytes: number) => {
|
|
|
|
|
if (bytes === 0) return '0 Bytes';
|
|
|
|
|
const k = 1024;
|
|
|
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
interface Folder {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
|
|
|
|
parentId: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface UploadViewProps {
|
|
|
|
|
user: any;
|
|
|
|
|
folders?: Folder[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function UploadView({ user, folders = [] }: UploadViewProps) {
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
// Form State
|
2026-01-08 05:41:31 +00:00
|
|
|
const [file, setFile] = useState<File | null>(null);
|
2026-01-09 06:58:44 +00:00
|
|
|
const [description, setDescription] = useState("");
|
|
|
|
|
const [parentId, setParentId] = useState("");
|
2026-01-08 05:41:31 +00:00
|
|
|
const [status, setStatus] = useState<'idle' | 'uploading' | 'success'>('idle');
|
|
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
// Folder Creation State
|
|
|
|
|
const [showFolderInput, setShowFolderInput] = useState(false);
|
|
|
|
|
const [newFolderName, setNewFolderName] = useState("");
|
2026-01-11 13:41:54 +00:00
|
|
|
const [isCreatingFolder, setIsCreatingFolder] = useState(false);
|
2026-01-09 06:58:44 +00:00
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
const MAX_FILE_SIZE = 150 * 1024 * 1024; // 150MB Limit
|
2026-01-09 06:58:44 +00:00
|
|
|
|
|
|
|
|
const handleCreateFolder = async () => {
|
|
|
|
|
if (!newFolderName.trim()) return;
|
2026-01-11 13:41:54 +00:00
|
|
|
setIsCreatingFolder(true);
|
2026-01-09 06:58:44 +00:00
|
|
|
try {
|
2026-01-11 13:41:54 +00:00
|
|
|
// Creates folder nested under current selection if parentId exists
|
|
|
|
|
await createFolderAction(newFolderName, parentId || null);
|
2026-01-09 06:58:44 +00:00
|
|
|
setNewFolderName("");
|
|
|
|
|
setShowFolderInput(false);
|
2026-01-11 13:41:54 +00:00
|
|
|
router.refresh();
|
2026-01-09 06:58:44 +00:00
|
|
|
} catch (err) {
|
|
|
|
|
alert("Error creating folder");
|
2026-01-11 13:41:54 +00:00
|
|
|
} finally {
|
|
|
|
|
setIsCreatingFolder(false);
|
2026-01-09 06:58:44 +00:00
|
|
|
}
|
|
|
|
|
};
|
2026-01-08 05:41:31 +00:00
|
|
|
|
|
|
|
|
const handleUpload = async () => {
|
|
|
|
|
if (!file) return;
|
|
|
|
|
if (file.size > MAX_FILE_SIZE) {
|
2026-01-11 13:41:54 +00:00
|
|
|
alert(`File is too large! Max allowed: ${formatFileSize(MAX_FILE_SIZE)}.`);
|
2026-01-09 06:58:44 +00:00
|
|
|
return;
|
2026-01-08 05:41:31 +00:00
|
|
|
}
|
2026-01-09 06:58:44 +00:00
|
|
|
|
2026-01-08 05:41:31 +00:00
|
|
|
setStatus('uploading');
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("file", file);
|
2026-01-09 06:58:44 +00:00
|
|
|
formData.append("description", description);
|
|
|
|
|
formData.append("parentId", parentId);
|
2026-01-08 05:41:31 +00:00
|
|
|
|
|
|
|
|
try {
|
2026-01-11 13:41:54 +00:00
|
|
|
const result = await uploadFileAction(formData);
|
|
|
|
|
if (result.success) {
|
|
|
|
|
setStatus('success');
|
|
|
|
|
setFile(null);
|
|
|
|
|
setDescription("");
|
|
|
|
|
router.refresh();
|
|
|
|
|
}
|
2026-01-08 05:41:31 +00:00
|
|
|
} catch (err) {
|
2026-01-09 06:58:44 +00:00
|
|
|
console.error(err);
|
2026-01-11 13:41:54 +00:00
|
|
|
alert("Upload failed. Check server logs.");
|
2026-01-08 05:41:31 +00:00
|
|
|
setStatus('idle');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
2026-01-11 13:41:54 +00:00
|
|
|
<Paper sx={{ p: { xs: 3, md: 6 }, borderRadius: 4, maxWidth: 800, mx: 'auto' }} elevation={3}>
|
2026-01-09 06:58:44 +00:00
|
|
|
<Typography variant="h4" fontWeight={900} color="primary" gutterBottom align="center">
|
|
|
|
|
Add to Library
|
2026-01-08 05:41:31 +00:00
|
|
|
</Typography>
|
|
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
<Stack spacing={4} sx={{ mt: 4 }}>
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
{/* Section 1: Destination Folder */}
|
2026-01-09 06:58:44 +00:00
|
|
|
<Box>
|
2026-01-11 13:41:54 +00:00
|
|
|
<Typography variant="h6" gutterBottom fontWeight="700" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<FolderIcon color="primary" /> 1. Destination
|
2026-01-09 06:58:44 +00:00
|
|
|
</Typography>
|
2026-01-11 13:41:54 +00:00
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
<Stack direction="row" spacing={1}>
|
|
|
|
|
<TextField
|
|
|
|
|
select
|
|
|
|
|
fullWidth
|
2026-01-11 13:41:54 +00:00
|
|
|
label="Target Project / Folder"
|
2026-01-09 06:58:44 +00:00
|
|
|
value={parentId}
|
|
|
|
|
onChange={(e) => setParentId(e.target.value)}
|
|
|
|
|
disabled={status === 'uploading'}
|
2026-01-11 13:41:54 +00:00
|
|
|
helperText="Files will be virtually organized into this folder."
|
2026-01-09 06:58:44 +00:00
|
|
|
>
|
2026-01-11 13:41:54 +00:00
|
|
|
<MenuItem value=""><em>-- Root (Main Library) --</em></MenuItem>
|
2026-01-09 06:58:44 +00:00
|
|
|
{folders.map((f) => (
|
2026-01-11 13:41:54 +00:00
|
|
|
<MenuItem key={f.id} value={f.id} sx={{ pl: f.parentId ? 4 : 2 }}>
|
|
|
|
|
{f.parentId ? `↳ ${f.name}` : f.name}
|
|
|
|
|
</MenuItem>
|
2026-01-09 06:58:44 +00:00
|
|
|
))}
|
|
|
|
|
</TextField>
|
2026-01-11 13:41:54 +00:00
|
|
|
<Tooltip title="Create New Folder Inside Selected">
|
2026-01-09 06:58:44 +00:00
|
|
|
<IconButton
|
|
|
|
|
color="primary"
|
|
|
|
|
onClick={() => setShowFolderInput(!showFolderInput)}
|
2026-01-11 13:41:54 +00:00
|
|
|
sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 1, width: 56, height: 56 }}
|
2026-01-09 06:58:44 +00:00
|
|
|
>
|
|
|
|
|
<CreateNewFolderIcon />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Stack>
|
2026-01-08 05:41:31 +00:00
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
{showFolderInput && (
|
2026-01-11 13:41:54 +00:00
|
|
|
<Stack direction="row" spacing={1} sx={{ mt: 2, p: 2, bgcolor: 'action.hover', borderRadius: 2 }}>
|
2026-01-09 06:58:44 +00:00
|
|
|
<TextField
|
|
|
|
|
size="small"
|
|
|
|
|
fullWidth
|
|
|
|
|
placeholder="New folder name..."
|
|
|
|
|
value={newFolderName}
|
|
|
|
|
onChange={(e) => setNewFolderName(e.target.value)}
|
|
|
|
|
autoFocus
|
2026-01-11 13:41:54 +00:00
|
|
|
disabled={isCreatingFolder}
|
2026-01-09 06:58:44 +00:00
|
|
|
/>
|
2026-01-11 13:41:54 +00:00
|
|
|
<Button
|
|
|
|
|
variant="contained"
|
|
|
|
|
onClick={handleCreateFolder}
|
|
|
|
|
disabled={isCreatingFolder || !newFolderName.trim()}
|
|
|
|
|
>
|
|
|
|
|
{isCreatingFolder ? "..." : "Create"}
|
|
|
|
|
</Button>
|
2026-01-09 06:58:44 +00:00
|
|
|
</Stack>
|
2026-01-08 05:41:31 +00:00
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
<Divider />
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
{/* Section 2: File Upload Area */}
|
2026-01-09 06:58:44 +00:00
|
|
|
<Box>
|
2026-01-11 13:41:54 +00:00
|
|
|
<Typography variant="h6" gutterBottom fontWeight="700" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<CloudUploadIcon color="primary" /> 2. Upload File
|
2026-01-09 06:58:44 +00:00
|
|
|
</Typography>
|
2026-01-11 13:41:54 +00:00
|
|
|
|
|
|
|
|
<Box
|
|
|
|
|
sx={{
|
|
|
|
|
p: 5,
|
|
|
|
|
mt: 1,
|
|
|
|
|
border: '2px dashed',
|
|
|
|
|
borderColor: file ? 'primary.main' : 'divider',
|
|
|
|
|
borderRadius: 3,
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
bgcolor: file ? 'rgba(25, 118, 210, 0.04)' : '#fafafa',
|
|
|
|
|
'&:hover': { bgcolor: 'rgba(0, 0, 0, 0.02)' }
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-01-09 06:58:44 +00:00
|
|
|
<input
|
|
|
|
|
type="file" id="file-input" hidden
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
setFile(e.target.files?.[0] || null);
|
|
|
|
|
setStatus('idle');
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<label htmlFor="file-input">
|
2026-01-11 13:41:54 +00:00
|
|
|
<Button
|
|
|
|
|
variant={file ? "outlined" : "contained"}
|
|
|
|
|
component="span"
|
|
|
|
|
startIcon={<CloudUploadIcon />}
|
|
|
|
|
size="large"
|
|
|
|
|
disabled={status === 'uploading'}
|
|
|
|
|
>
|
|
|
|
|
{file ? "Change File" : "Choose Document"}
|
2026-01-09 06:58:44 +00:00
|
|
|
</Button>
|
|
|
|
|
</label>
|
2026-01-11 13:41:54 +00:00
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
{file && (
|
|
|
|
|
<Box mt={2}>
|
2026-01-11 13:41:54 +00:00
|
|
|
<Typography variant="subtitle2" color="primary.main" fontWeight="bold">
|
|
|
|
|
{file.name}
|
2026-01-09 06:58:44 +00:00
|
|
|
</Typography>
|
2026-01-11 13:41:54 +00:00
|
|
|
<Typography variant="caption" color="text.secondary">
|
|
|
|
|
{formatFileSize(file.size)}
|
2026-01-09 06:58:44 +00:00
|
|
|
</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
</Box>
|
|
|
|
|
|
2026-01-11 13:41:54 +00:00
|
|
|
{/* Section 3: Notes */}
|
|
|
|
|
<Box>
|
|
|
|
|
<Typography variant="h6" gutterBottom fontWeight="700" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<AssignmentIcon color="primary" /> 3. Metadata
|
2026-01-08 05:41:31 +00:00
|
|
|
</Typography>
|
2026-01-11 13:41:54 +00:00
|
|
|
<TextField
|
|
|
|
|
label="Description / Notes"
|
|
|
|
|
multiline rows={3} fullWidth
|
|
|
|
|
placeholder="Add keywords or a brief summary..."
|
|
|
|
|
value={description}
|
|
|
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
|
|
|
disabled={status === 'uploading'}
|
|
|
|
|
/>
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
{/* Progress & Actions */}
|
|
|
|
|
<Box>
|
|
|
|
|
{status === 'uploading' && (
|
|
|
|
|
<Box mb={2}>
|
|
|
|
|
<LinearProgress sx={{ borderRadius: 5, height: 10 }} />
|
|
|
|
|
<Typography variant="caption" color="primary" sx={{ mt: 1, display: 'block', textAlign: 'center' }}>
|
|
|
|
|
Transferring to OneDrive and updating Library...
|
|
|
|
|
</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
variant="contained" size="large" fullWidth
|
|
|
|
|
disabled={!file || status === 'uploading'}
|
|
|
|
|
onClick={handleUpload}
|
|
|
|
|
sx={{ py: 2, fontWeight: 'bold' }}
|
|
|
|
|
>
|
|
|
|
|
{status === 'uploading' ? 'Uploading...' : 'Add to Library'}
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
{status === 'success' && (
|
|
|
|
|
<Typography align="center" color="success.main" fontWeight="bold" sx={{ mt: 2 }}>
|
|
|
|
|
✅ File processed and assigned to project!
|
|
|
|
|
</Typography>
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
2026-01-08 05:41:31 +00:00
|
|
|
</Stack>
|
|
|
|
|
</Paper>
|
|
|
|
|
);
|
|
|
|
|
}
|