2026-01-08 05:41:31 +00:00
|
|
|
'use client';
|
|
|
|
|
|
2026-01-13 13:17:40 +00:00
|
|
|
import { useState, useRef } from "react";
|
2026-01-09 06:58:44 +00:00
|
|
|
import {
|
2026-01-13 13:17:40 +00:00
|
|
|
Box, Button, Typography, Paper, Stack,
|
|
|
|
|
TextField, MenuItem, IconButton, Divider,
|
|
|
|
|
Grid, CircularProgress
|
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-11 13:41:54 +00:00
|
|
|
import FolderIcon from "@mui/icons-material/Folder";
|
2026-01-13 13:17:40 +00:00
|
|
|
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
|
2026-01-11 13:41:54 +00:00
|
|
|
import AssignmentIcon from '@mui/icons-material/Assignment';
|
2026-01-12 11:53:20 +00:00
|
|
|
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
|
|
|
|
|
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
2026-01-11 13:41:54 +00:00
|
|
|
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-12 11:53:20 +00:00
|
|
|
interface MetadataPair {
|
|
|
|
|
key: string;
|
|
|
|
|
value: string;
|
2026-01-11 13:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-12 11:53:20 +00:00
|
|
|
export default function UploadView({ user, folders = [] }: any) {
|
2026-01-11 13:41:54 +00:00
|
|
|
const router = useRouter();
|
2026-01-13 13:17:40 +00:00
|
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
2026-01-11 13:41:54 +00:00
|
|
|
|
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-12 11:53:20 +00:00
|
|
|
const [customMetadata, setCustomMetadata] = useState<MetadataPair[]>([]);
|
2026-01-08 05:41:31 +00:00
|
|
|
|
2026-01-13 13:17:40 +00:00
|
|
|
// Folder Creation State
|
2026-01-09 06:58:44 +00:00
|
|
|
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-13 13:17:40 +00:00
|
|
|
const handleCreateFolder = async () => {
|
|
|
|
|
if (!newFolderName.trim()) return;
|
|
|
|
|
setIsCreatingFolder(true);
|
|
|
|
|
try {
|
|
|
|
|
// FIX: Pass the current parentId to the action so it nests correctly
|
|
|
|
|
const result = await createFolderAction(newFolderName, parentId);
|
|
|
|
|
if (result.success) {
|
|
|
|
|
setNewFolderName("");
|
|
|
|
|
setShowFolderInput(false);
|
|
|
|
|
router.refresh();
|
|
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
alert(err.message || "Failed to create folder");
|
|
|
|
|
} finally {
|
|
|
|
|
setIsCreatingFolder(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-12 11:53:20 +00:00
|
|
|
const addMetadataRow = () => setCustomMetadata([...customMetadata, { key: "", value: "" }]);
|
|
|
|
|
|
|
|
|
|
const removeMetadataRow = (index: number) => {
|
|
|
|
|
setCustomMetadata(customMetadata.filter((_, i) => i !== index));
|
|
|
|
|
};
|
2026-01-09 06:58:44 +00:00
|
|
|
|
2026-01-12 11:53:20 +00:00
|
|
|
const updateMetadataRow = (index: number, field: 'key' | 'value', val: string) => {
|
|
|
|
|
const updated = [...customMetadata];
|
|
|
|
|
updated[index][field] = val;
|
|
|
|
|
setCustomMetadata(updated);
|
2026-01-09 06:58:44 +00:00
|
|
|
};
|
2026-01-08 05:41:31 +00:00
|
|
|
|
|
|
|
|
const handleUpload = async () => {
|
|
|
|
|
if (!file) return;
|
|
|
|
|
setStatus('uploading');
|
2026-01-12 11:53:20 +00:00
|
|
|
|
2026-01-08 05:41:31 +00:00
|
|
|
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-12 11:53:20 +00:00
|
|
|
|
|
|
|
|
const metadataObj = customMetadata.reduce((acc, curr) => {
|
|
|
|
|
if (curr.key.trim()) acc[curr.key.trim()] = curr.value;
|
|
|
|
|
return acc;
|
|
|
|
|
}, {} as Record<string, string>);
|
|
|
|
|
|
|
|
|
|
formData.append("customMetadata", JSON.stringify(metadataObj));
|
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("");
|
2026-01-12 11:53:20 +00:00
|
|
|
setCustomMetadata([]);
|
2026-01-13 13:17:40 +00:00
|
|
|
router.push("/dashboard");
|
2026-01-11 13:41:54 +00:00
|
|
|
router.refresh();
|
|
|
|
|
}
|
2026-01-08 05:41:31 +00:00
|
|
|
} catch (err) {
|
2026-01-12 11:53:20 +00:00
|
|
|
alert("Upload failed.");
|
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-13 13:17:40 +00:00
|
|
|
{/* 1. Destination */}
|
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-13 13:17:40 +00:00
|
|
|
|
2026-01-09 06:58:44 +00:00
|
|
|
<Stack direction="row" spacing={1}>
|
|
|
|
|
<TextField
|
2026-01-13 13:17:40 +00:00
|
|
|
id="project-destination-select"
|
|
|
|
|
select
|
|
|
|
|
fullWidth
|
|
|
|
|
label="Target Project / Folder"
|
2026-01-09 06:58:44 +00:00
|
|
|
value={parentId}
|
|
|
|
|
onChange={(e) => setParentId(e.target.value)}
|
2026-01-13 13:17:40 +00:00
|
|
|
size="small"
|
|
|
|
|
slotProps={{
|
|
|
|
|
select: { displayEmpty: true },
|
|
|
|
|
inputLabel: { shrink: true },
|
|
|
|
|
}}
|
2026-01-09 06:58:44 +00:00
|
|
|
>
|
2026-01-13 13:17:40 +00:00
|
|
|
<MenuItem value=""><em>-- Root (Main Folder) --</em></MenuItem>
|
2026-01-12 11:53:20 +00:00
|
|
|
{folders.map((f: any) => (
|
|
|
|
|
<MenuItem key={f.id} value={f.id}>{f.name}</MenuItem>
|
2026-01-09 06:58:44 +00:00
|
|
|
))}
|
|
|
|
|
</TextField>
|
2026-01-13 13:17:40 +00:00
|
|
|
<IconButton
|
|
|
|
|
color="primary"
|
|
|
|
|
onClick={() => setShowFolderInput(!showFolderInput)}
|
|
|
|
|
sx={{ border: '1px solid #ccc', borderRadius: 1 }}
|
|
|
|
|
>
|
2026-01-12 11:53:20 +00:00
|
|
|
<CreateNewFolderIcon />
|
|
|
|
|
</IconButton>
|
2026-01-09 06:58:44 +00:00
|
|
|
</Stack>
|
2026-01-13 13:17:40 +00:00
|
|
|
|
|
|
|
|
{showFolderInput && (
|
|
|
|
|
<Box sx={{ mt: 2, p: 2, bgcolor: '#f8f9fa', borderRadius: 2 }}>
|
|
|
|
|
<Typography variant="subtitle2" gutterBottom>
|
|
|
|
|
{parentId ? `Create inside current selection` : `Create at Root`}
|
|
|
|
|
</Typography>
|
|
|
|
|
<Stack direction="row" spacing={1}>
|
|
|
|
|
<TextField
|
|
|
|
|
fullWidth size="small" placeholder="Folder Name (e.g. Project-2)"
|
|
|
|
|
value={newFolderName}
|
|
|
|
|
onChange={(e) => setNewFolderName(e.target.value)}
|
|
|
|
|
onKeyPress={(e) => e.key === 'Enter' && handleCreateFolder()}
|
|
|
|
|
/>
|
|
|
|
|
<Button
|
|
|
|
|
variant="contained"
|
|
|
|
|
onClick={handleCreateFolder}
|
|
|
|
|
disabled={isCreatingFolder || !newFolderName}
|
|
|
|
|
>
|
|
|
|
|
{isCreatingFolder ? <CircularProgress size={24} /> : "Create"}
|
|
|
|
|
</Button>
|
|
|
|
|
</Stack>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
2026-01-08 05:41:31 +00:00
|
|
|
</Box>
|
|
|
|
|
|
2026-01-13 13:17:40 +00:00
|
|
|
{/* 2. Upload Area */}
|
2026-01-09 06:58:44 +00:00
|
|
|
<Box>
|
2026-01-13 13:17:40 +00:00
|
|
|
<Typography variant="h6" gutterBottom fontWeight="700" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
2026-01-11 13:41:54 +00:00
|
|
|
<CloudUploadIcon color="primary" /> 2. Upload File
|
2026-01-09 06:58:44 +00:00
|
|
|
</Typography>
|
2026-01-13 13:17:40 +00:00
|
|
|
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
ref={fileInputRef}
|
|
|
|
|
style={{ display: 'none' }}
|
|
|
|
|
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
variant="outlined"
|
|
|
|
|
fullWidth
|
|
|
|
|
sx={{ p: 4, borderStyle: 'dashed', textTransform: 'none' }}
|
|
|
|
|
onClick={() => fileInputRef.current?.click()}
|
|
|
|
|
>
|
|
|
|
|
{file ? (
|
|
|
|
|
<Box>
|
|
|
|
|
<Typography color="success.main" fontWeight="bold">✓ {file.name}</Typography>
|
|
|
|
|
<Typography variant="caption" color="text.secondary">
|
|
|
|
|
Click to change file ({(file.size / 1024 / 1024).toFixed(2)} MB)
|
|
|
|
|
</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
) : (
|
|
|
|
|
"Click to Select File"
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
2026-01-09 06:58:44 +00:00
|
|
|
</Box>
|
|
|
|
|
|
2026-01-13 13:17:40 +00:00
|
|
|
{/* 3. Custom Attributes */}
|
2026-01-11 13:41:54 +00:00
|
|
|
<Box>
|
2026-01-12 11:53:20 +00:00
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
|
|
|
|
<Typography variant="h6" fontWeight="700" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<AssignmentIcon color="primary" /> 3. Custom Attributes
|
2026-01-11 13:41:54 +00:00
|
|
|
</Typography>
|
2026-01-12 11:53:20 +00:00
|
|
|
<Button startIcon={<AddCircleOutlineIcon />} size="small" onClick={addMetadataRow}>
|
|
|
|
|
Add Field
|
|
|
|
|
</Button>
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
<Stack spacing={2}>
|
|
|
|
|
{customMetadata.map((row, index) => (
|
|
|
|
|
<Grid container spacing={1} key={index} alignItems="center">
|
|
|
|
|
<Grid item xs={5}>
|
|
|
|
|
<TextField
|
2026-01-13 13:17:40 +00:00
|
|
|
fullWidth size="small" placeholder="Key (e.g. Project-ID)"
|
2026-01-12 11:53:20 +00:00
|
|
|
value={row.key} onChange={(e) => updateMetadataRow(index, 'key', e.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</Grid>
|
|
|
|
|
<Grid item xs={6}>
|
|
|
|
|
<TextField
|
|
|
|
|
fullWidth size="small" placeholder="Value"
|
|
|
|
|
value={row.value} onChange={(e) => updateMetadataRow(index, 'value', e.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</Grid>
|
|
|
|
|
<Grid item xs={1}>
|
|
|
|
|
<IconButton color="error" onClick={() => removeMetadataRow(index)}>
|
|
|
|
|
<DeleteOutlineIcon />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Grid>
|
|
|
|
|
</Grid>
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
<TextField
|
|
|
|
|
label="General Description"
|
|
|
|
|
multiline rows={2} fullWidth
|
|
|
|
|
value={description}
|
|
|
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</Stack>
|
2026-01-11 13:41:54 +00:00
|
|
|
</Box>
|
2026-01-12 11:53:20 +00:00
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
variant="contained" size="large" fullWidth
|
|
|
|
|
disabled={!file || status === 'uploading'}
|
|
|
|
|
onClick={handleUpload}
|
2026-01-13 13:17:40 +00:00
|
|
|
sx={{ py: 2, fontWeight: 'bold' }}
|
2026-01-12 11:53:20 +00:00
|
|
|
>
|
2026-01-13 13:17:40 +00:00
|
|
|
{status === 'uploading' ? 'Uploading to OneDrive...' : 'Start Upload'}
|
2026-01-12 11:53:20 +00:00
|
|
|
</Button>
|
2026-01-08 05:41:31 +00:00
|
|
|
</Stack>
|
|
|
|
|
</Paper>
|
|
|
|
|
);
|
|
|
|
|
}
|