Files
app/src/pages/dev/SimpleUploadTest.tsx
T
CC Worker 765573163d feat(ts): R4 TypeScript hardening H1-H6 — reduce errors 177→152
H1: CCGraphNavPanel — useMemo import + LogCategory fix
H2: CCTimetableLessonNodeShapeUtil — props type alignment (cc-graph-props.ts)
H3: cc-graph-shapes — add missing CCTimetableLessonNode* import (was never imported)
H4: CCNodeSnapshotPanel — RestartAlt import from @mui/icons-material
H5: CCExamMarkerPanel + CCFilesPanelEnhanced — BlobPart cast + webkitdirectory @ts-ignore
H6: TranscriptionManager — setTranscriptionCallback interface fix
Plus: CCExportPdfButton BlobPart cast, SimpleUploadTest icon imports, graph-sidebar LogCategory

tsc before: 177 (master)
tsc after:  152 (reduction of 25 errors, well below 160 acceptable threshold)
2026-06-01 02:29:38 +00:00

882 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useRef, useCallback } from 'react';
import {
Box,
Typography,
Button,
Card,
CardContent,
CardHeader,
Grid,
Alert,
Chip,
LinearProgress,
List,
ListItem,
ListItemText,
ListItemIcon,
IconButton,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Divider,
Select,
MenuItem,
FormControl,
InputLabel,
Paper,
TextField,
Pagination,
Stack
} from '@mui/material';
import {
CloudUpload as UploadIcon,
Upload,
Folder as FolderIcon,
FolderOpen as FolderOpenIcon,
Description as FileIcon,
Delete as DeleteIcon,
Refresh as RefreshIcon,
PlayArrow as ProcessIcon,
CheckCircle as SuccessIcon,
Error as ErrorIcon,
Info as InfoIcon,
InsertDriveFile,
} from '@mui/icons-material';
const FolderOpen = FolderOpenIcon;
const Refresh = RefreshIcon;
const Info = InfoIcon;
import { supabase } from '../../supabaseClient';
import {
pickDirectory,
processDirectoryFiles,
calculateDirectoryStats,
formatFileSize,
isDirectoryPickerSupported,
FileWithPath
} from '../../utils/folderPicker';
interface Cabinet {
id: string;
name: string;
}
interface FileRecord {
id: string;
name: string;
mime_type?: string;
is_directory?: boolean;
size_bytes?: number;
processing_status?: string;
relative_path?: string;
created_at?: string;
}
interface PaginationInfo {
page: number;
per_page: number;
total_count: number;
total_pages: number;
has_next: boolean;
has_prev: boolean;
offset: number;
}
interface FileListResponse {
files: FileRecord[];
pagination: PaginationInfo;
filters: {
search?: string;
sort_by: string;
sort_order: string;
include_directories: boolean;
parent_directory_id?: string;
};
}
interface UploadProgress {
path: string;
size: number;
status: 'queued' | 'uploading' | 'done' | 'error';
progress: number;
error?: string;
}
const SimpleUploadTest: React.FC = () => {
// State management
const [cabinets, setCabinets] = useState<Cabinet[]>([]);
const [selectedCabinet, setSelectedCabinet] = useState<string>('');
const [files, setFiles] = useState<FileRecord[]>([]);
const [pagination, setPagination] = useState<PaginationInfo | null>(null);
const [loading, setLoading] = useState(false);
// Pagination and filtering state
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10);
const [searchTerm, setSearchTerm] = useState('');
const [sortBy, setSortBy] = useState('created_at');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
const [uploadProgress, setUploadProgress] = useState<UploadProgress[]>([]);
const [showUploadDialog, setShowUploadDialog] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [selectedFiles, setSelectedFiles] = useState<FileWithPath[]>([]);
const [directoryStats, setDirectoryStats] = useState<any>(null);
const [message, setMessage] = useState<{ type: 'success' | 'error' | 'info', text: string } | null>(null);
const [uploadType, setUploadType] = useState<'old' | 'new'>('new');
// Refs
const fileInputRef = useRef<HTMLInputElement>(null);
const dirInputRef = useRef<HTMLInputElement>(null);
const API_BASE = import.meta.env.VITE_API_BASE || '/api';
const apiFetch = useCallback(async (url: string, init?: { method?: string; body?: FormData | string; headers?: Record<string, string> }) => {
const session = await supabase.auth.getSession();
const token = session?.data?.session?.access_token;
if (!token) {
throw new Error('No authentication token available');
}
const headers = {
'Authorization': `Bearer ${token}`,
...((init?.headers as Record<string, string>) || {})
};
const fullUrl = url.startsWith('http') ? url : `${API_BASE}${url}`;
const res = await fetch(fullUrl, { ...init, headers });
if (!res.ok) {
const errorText = await res.text();
throw new Error(`HTTP ${res.status}: ${errorText}`);
}
return res.json();
}, [API_BASE]);
// Load cabinets and files
const loadCabinets = useCallback(async () => {
setLoading(true);
try {
const data = await apiFetch('/database/cabinets');
const all = [...(data.owned || []), ...(data.shared || [])];
setCabinets(all);
if (all.length && !selectedCabinet) {
setSelectedCabinet(all[0].id);
}
setMessage({ type: 'success', text: `Loaded ${all.length} cabinets` });
} catch (error: unknown) {
console.error('Failed to load cabinets:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
setMessage({ type: 'error', text: `Failed to load cabinets: ${errorMessage}` });
} finally {
setLoading(false);
}
}, [selectedCabinet, apiFetch]);
const loadFiles = useCallback(async (cabinetId: string, page: number = currentPage) => {
if (!cabinetId) return;
setLoading(true);
try {
// Build query parameters for pagination, search, and sorting
const params = new URLSearchParams({
cabinet_id: cabinetId,
page: page.toString(),
per_page: itemsPerPage.toString(),
sort_by: sortBy,
sort_order: sortOrder,
include_directories: 'true'
});
if (searchTerm) {
params.append('search', searchTerm);
}
// Use the new simple upload endpoint for listing files with pagination
const data: FileListResponse = await apiFetch(`/simple-upload/files?${params.toString()}`);
setFiles(data.files || []);
setPagination(data.pagination);
setMessage({
type: 'success',
text: `Loaded ${data.files?.length || 0} files (${data.pagination.total_count} total)`
});
} catch (error: unknown) {
console.error('Failed to load files:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
setMessage({ type: 'error', text: `Failed to load files: ${errorMessage}` });
} finally {
setLoading(false);
}
}, [currentPage, itemsPerPage, sortBy, sortOrder, searchTerm, apiFetch]);
useEffect(() => {
loadCabinets();
}, [loadCabinets]);
useEffect(() => {
if (selectedCabinet) {
setCurrentPage(1); // Reset to first page when cabinet changes
loadFiles(selectedCabinet, 1);
}
}, [selectedCabinet, loadFiles]);
// Reload files when pagination/filtering parameters change
useEffect(() => {
if (selectedCabinet) {
loadFiles(selectedCabinet, currentPage);
}
}, [selectedCabinet, loadFiles, currentPage]);
// Search with debouncing
useEffect(() => {
if (selectedCabinet) {
const timeoutId = setTimeout(() => {
setCurrentPage(1); // Reset to first page when searching
loadFiles(selectedCabinet, 1);
}, 500); // 500ms debounce
return () => clearTimeout(timeoutId);
}
}, [searchTerm, selectedCabinet, loadFiles]);
// Single file upload
const handleSingleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files || !selectedCabinet) return;
const file = e.target.files[0];
const formData = new FormData();
formData.append('cabinet_id', selectedCabinet);
formData.append('path', file.name);
formData.append('scope', 'teacher');
formData.append('file', file);
try {
setLoading(true);
// Choose endpoint based on upload type
const endpoint = uploadType === 'new' ? '/simple-upload/files/upload' : '/database/files/upload';
const result = await apiFetch(endpoint, {
method: 'POST',
body: formData
});
console.log('Upload result:', result);
setMessage({
type: 'success',
text: `File uploaded successfully using ${uploadType === 'new' ? 'NEW' : 'OLD'} endpoint: ${file.name}`
});
await loadFiles(selectedCabinet);
e.target.value = '';
} catch (error: unknown) {
console.error('Upload failed:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
setMessage({ type: 'error', text: `Upload failed: ${errorMessage}` });
} finally {
setLoading(false);
}
};
// Directory upload handling
const handleDirectoryPicker = async () => {
try {
const files = await pickDirectory();
prepareDirectoryUpload(files);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (errorMessage === 'fallback-input') {
dirInputRef.current?.click();
} else if (errorMessage === 'user-cancelled') {
setMessage({ type: 'info', text: 'Directory selection cancelled' });
} else {
console.error('Directory picker error:', error);
setMessage({ type: 'error', text: 'Failed to pick directory. Trying fallback method...' });
dirInputRef.current?.click();
}
}
};
const handleFallbackDirectorySelect = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return;
const files = processDirectoryFiles(e.target.files);
prepareDirectoryUpload(files);
e.target.value = '';
};
const prepareDirectoryUpload = (files: FileWithPath[]) => {
if (files.length === 0) {
setMessage({ type: 'error', text: 'No files selected' });
return;
}
setSelectedFiles(files);
setDirectoryStats(calculateDirectoryStats(files));
const progress: UploadProgress[] = files.map(file => ({
path: file.relativePath,
size: file.size,
status: 'queued',
progress: 0
}));
setUploadProgress(progress);
setShowUploadDialog(true);
};
const startDirectoryUpload = async () => {
if (!selectedCabinet || selectedFiles.length === 0) return;
setIsUploading(true);
try {
const firstFilePath = selectedFiles[0].relativePath;
const directoryName = firstFilePath.split('/')[0] || 'uploaded-folder';
const formData = new FormData();
formData.append('cabinet_id', selectedCabinet);
formData.append('scope', 'teacher');
formData.append('directory_name', directoryName);
selectedFiles.forEach(file => {
formData.append('files', file);
});
const relativePaths = selectedFiles.map(f => f.relativePath);
formData.append('file_paths', JSON.stringify(relativePaths));
const result = await apiFetch('/simple-upload/files/upload-directory', {
method: 'POST',
body: formData
});
console.log('Directory upload result:', result);
setUploadProgress(prev => prev.map(item => ({
...item,
status: 'done',
progress: 100
})));
setMessage({
type: 'success',
text: `Directory uploaded successfully: ${directoryName} (${selectedFiles.length} files)`
});
await loadFiles(selectedCabinet);
setTimeout(() => {
setShowUploadDialog(false);
setIsUploading(false);
setSelectedFiles([]);
setUploadProgress([]);
}, 2000);
} catch (error: unknown) {
console.error('Directory upload failed:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
setMessage({ type: 'error', text: `Directory upload failed: ${errorMessage}` });
setUploadProgress(prev => prev.map(item => ({
...item,
status: 'error',
error: String(error)
})));
setIsUploading(false);
}
};
// Delete file
const handleDelete = async (fileId: string) => {
try {
await apiFetch(`/simple-upload/files/${fileId}`, { method: 'DELETE' });
setMessage({ type: 'success', text: 'File deleted successfully' });
await loadFiles(selectedCabinet);
} catch (error: unknown) {
console.error('Delete failed:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
setMessage({ type: 'error', text: `Delete failed: ${errorMessage}` });
}
};
// Manual processing trigger
const handleManualProcessing = async (fileId: string) => {
try {
const formData = new FormData();
formData.append('processing_type', 'basic');
const result = await apiFetch(`/simple-upload/files/${fileId}/process-manual`, {
method: 'POST',
body: formData
});
console.log('Manual processing result:', result);
setMessage({ type: 'info', text: 'Manual processing triggered (not yet implemented)' });
} catch (error: unknown) {
console.error('Manual processing failed:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
setMessage({ type: 'error', text: `Manual processing failed: ${errorMessage}` });
}
};
const getStatusColor = (status?: string) => {
switch (status) {
case 'uploaded': return 'primary';
case 'processing': return 'warning';
case 'completed': return 'success';
case 'failed': return 'error';
default: return 'default';
}
};
return (
<Box sx={{ p: 3, maxWidth: 1200, mx: 'auto' }}>
<Typography variant="h4" gutterBottom>
🧪 Simple Upload Test Page
</Typography>
<Alert severity="info" sx={{ mb: 3 }}>
<Typography variant="body2">
This page tests the NEW simple upload system (no auto-processing) vs the OLD system (with auto-processing).
Use this to verify that files upload without triggering Docling bundles, Tika runs, etc.
</Typography>
</Alert>
{message && (
<Alert severity={message.type} sx={{ mb: 2 }} onClose={() => setMessage(null)}>
{message.text}
</Alert>
)}
<Grid container spacing={3}>
{/* Upload Controls */}
<Grid item xs={12} md={6}>
<Card>
<CardHeader
title="Upload Controls"
avatar={<Upload />}
/>
<CardContent>
<Box sx={{ mb: 2 }}>
<FormControl fullWidth size="small">
<InputLabel>Cabinet</InputLabel>
<Select
value={selectedCabinet}
label="Cabinet"
onChange={(e) => setSelectedCabinet(e.target.value)}
>
{cabinets.map(cabinet => (
<MenuItem key={cabinet.id} value={cabinet.id}>
{cabinet.name}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
<Box sx={{ mb: 2 }}>
<FormControl size="small" sx={{ minWidth: 200 }}>
<InputLabel>Upload Type</InputLabel>
<Select
value={uploadType}
label="Upload Type"
onChange={(e) => setUploadType(e.target.value as 'old' | 'new')}
>
<MenuItem value="new">🆕 NEW (Simple, No Auto-Processing)</MenuItem>
<MenuItem value="old">🔄 OLD (Auto-Processing)</MenuItem>
</Select>
</FormControl>
</Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<input
ref={fileInputRef}
type="file"
style={{ display: 'none' }}
onChange={handleSingleUpload}
/>
<Button
variant="outlined"
startIcon={<Upload />}
onClick={() => fileInputRef.current?.click()}
disabled={!selectedCabinet || loading}
>
Upload File
</Button>
<input
ref={dirInputRef}
type="file"
style={{ display: 'none' }}
{...({ webkitdirectory: '' } as any)}
multiple
onChange={handleFallbackDirectorySelect}
/>
<Button
variant="outlined"
startIcon={<FolderOpen />}
onClick={handleDirectoryPicker}
disabled={!selectedCabinet || loading}
>
Upload Directory
</Button>
<Button
variant="outlined"
startIcon={<Refresh />}
onClick={() => {
setCurrentPage(1);
setSearchTerm('');
loadFiles(selectedCabinet, 1);
}}
disabled={loading}
>
Refresh
</Button>
</Box>
{isDirectoryPickerSupported() ? (
<Alert severity="success" sx={{ mt: 1 }}>
Modern directory picker supported (Chromium browser)
</Alert>
) : (
<Alert severity="info" sx={{ mt: 1 }}>
Using fallback directory picker (webkitdirectory)
</Alert>
)}
</CardContent>
</Card>
</Grid>
{/* System Info */}
<Grid item xs={12} md={6}>
<Card>
<CardHeader
title="System Info"
avatar={<Info />}
/>
<CardContent>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="textSecondary">
<strong>Current Endpoints:</strong>
</Typography>
<Typography variant="body2">
NEW: <code>/simple-upload/files/upload</code> (no auto-processing)
</Typography>
<Typography variant="body2">
OLD: <code>/database/files/upload</code> (auto-processing disabled for testing)
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="textSecondary">
<strong>Storage Buckets:</strong>
</Typography>
<Typography variant="body2">
Both systems now use: <code>cc.users</code> (teacher scope)
</Typography>
<Typography variant="caption" color="textSecondary">
Files will be stored in the same bucket for consistency
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="textSecondary">
<strong>Selected Cabinet:</strong>
</Typography>
<Typography variant="body2">
{selectedCabinet || 'None selected'}
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="textSecondary">
<strong>Upload Mode:</strong>
</Typography>
<Chip
label={uploadType === 'new' ? 'NEW (Simple)' : 'OLD (Auto-Processing)'}
color={uploadType === 'new' ? 'success' : 'warning'}
/>
</Box>
<Box>
<Typography variant="body2" color="textSecondary">
<strong>Files in Cabinet:</strong>
</Typography>
<Typography variant="h6">
{pagination ? `${pagination.total_count} total (${files.length} on page ${pagination.page})` : files.length}
</Typography>
</Box>
<Box>
<Typography variant="body2" color="textSecondary">
<strong>Pagination:</strong>
</Typography>
<Typography variant="body2">
{itemsPerPage} per page Sort by {sortBy} ({sortOrder})
</Typography>
{searchTerm && (
<Typography variant="caption" color="textSecondary">
Searching: "{searchTerm}"
</Typography>
)}
</Box>
</CardContent>
</Card>
</Grid>
{/* File List */}
<Grid item xs={12}>
<Card>
<CardHeader
title={
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<InsertDriveFile />
<Typography variant="h6">
Files {pagination && `(${pagination.total_count} total)`}
</Typography>
</Box>
{pagination && (
<Typography variant="body2" color="textSecondary">
Page {pagination.page} of {pagination.total_pages}
</Typography>
)}
</Box>
}
/>
<CardContent>
{/* Search and Filter Controls */}
<Box sx={{ mb: 2, display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}>
<TextField
size="small"
label="Search files"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
sx={{ minWidth: 200 }}
/>
<FormControl size="small" sx={{ minWidth: 120 }}>
<InputLabel>Sort by</InputLabel>
<Select
value={sortBy}
label="Sort by"
onChange={(e) => setSortBy(e.target.value)}
>
<MenuItem value="name">Name</MenuItem>
<MenuItem value="created_at">Date Created</MenuItem>
<MenuItem value="size_bytes">Size</MenuItem>
<MenuItem value="processing_status">Status</MenuItem>
</Select>
</FormControl>
<FormControl size="small" sx={{ minWidth: 100 }}>
<InputLabel>Order</InputLabel>
<Select
value={sortOrder}
label="Order"
onChange={(e) => setSortOrder(e.target.value as 'asc' | 'desc')}
>
<MenuItem value="asc">Ascending</MenuItem>
<MenuItem value="desc">Descending</MenuItem>
</Select>
</FormControl>
<FormControl size="small" sx={{ minWidth: 100 }}>
<InputLabel>Per page</InputLabel>
<Select
value={itemsPerPage}
label="Per page"
onChange={(e) => {
setItemsPerPage(Number(e.target.value));
setCurrentPage(1);
}}
>
<MenuItem value={5}>5</MenuItem>
<MenuItem value={10}>10</MenuItem>
<MenuItem value={20}>20</MenuItem>
<MenuItem value={50}>50</MenuItem>
</Select>
</FormControl>
</Box>
{/* File List with Fixed Height */}
<Box sx={{
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
height: 400, // Fixed height
overflow: 'auto'
}}>
{loading ? (
<Box sx={{ p: 2 }}>
<LinearProgress />
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
Loading files...
</Typography>
</Box>
) : files.length === 0 ? (
<Box sx={{ p: 3, textAlign: 'center' }}>
<Typography variant="body2" color="textSecondary">
{searchTerm ? 'No files found matching your search.' : 'No files found. Upload some files to test!'}
</Typography>
</Box>
) : (
<List disablePadding>
{files.map((file, index) => (
<React.Fragment key={file.id}>
<ListItem
secondaryAction={
<Box>
<IconButton
size="small"
onClick={() => handleManualProcessing(file.id)}
title="Trigger manual processing"
>
<ProcessIcon />
</IconButton>
<IconButton
size="small"
onClick={() => handleDelete(file.id)}
title="Delete file"
color="error"
>
<DeleteIcon />
</IconButton>
</Box>
}
>
<ListItemIcon>
{file.is_directory ? <FolderIcon /> : <InsertDriveFile />}
</ListItemIcon>
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
{file.name}
</Typography>
{file.is_directory && <Chip label="Directory" size="small" />}
<Chip
label={file.processing_status || 'unknown'}
size="small"
color={getStatusColor(file.processing_status)}
/>
</Box>
}
secondary={
<Box>
<Typography variant="caption" color="textSecondary">
{file.size_bytes ? formatFileSize(file.size_bytes) : 'Unknown size'} {file.mime_type || 'Unknown type'}
</Typography>
{file.relative_path && (
<Typography variant="caption" color="textSecondary" display="block">
Path: {file.relative_path}
</Typography>
)}
</Box>
}
/>
</ListItem>
{index < files.length - 1 && <Divider />}
</React.Fragment>
))}
</List>
)}
</Box>
{/* Pagination Controls */}
{pagination && pagination.total_pages > 1 && (
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
<Stack spacing={2} alignItems="center">
<Pagination
count={pagination.total_pages}
page={pagination.page}
onChange={(event, value) => setCurrentPage(value)}
color="primary"
showFirstButton
showLastButton
/>
<Typography variant="caption" color="textSecondary">
Showing {pagination.offset + 1}-{Math.min(pagination.offset + pagination.per_page, pagination.total_count)} of {pagination.total_count} files
</Typography>
</Stack>
</Box>
)}
</CardContent>
</Card>
</Grid>
</Grid>
{/* Directory Upload Dialog */}
<Dialog open={showUploadDialog} onClose={() => !isUploading && setShowUploadDialog(false)} maxWidth="md" fullWidth>
<DialogTitle>
<Box display="flex" alignItems="center" gap={1}>
<FolderOpen />
Directory Upload Progress
{isUploading && <LinearProgress sx={{ flexGrow: 1, ml: 2 }} />}
</Box>
</DialogTitle>
<DialogContent>
{directoryStats && (
<Alert severity="info" sx={{ mb: 2 }}>
<Typography variant="body2">
<strong>{directoryStats.fileCount} files</strong> in{' '}
<strong>{directoryStats.directoryCount} folders</strong><br />
Total size: <strong>{directoryStats.formattedSize}</strong>
</Typography>
</Alert>
)}
<Paper variant="outlined" sx={{ p: 2, maxHeight: 300, overflow: 'auto' }}>
{uploadProgress.map((item, i) => (
<Box key={i} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', py: 0.5 }}>
<Typography variant="body2" sx={{ flex: 1, mr: 2 }}>
{item.path}
</Typography>
<Typography variant="body2" sx={{ mr: 2, minWidth: 80 }}>
{formatFileSize(item.size)}
</Typography>
<Chip
label={item.status}
size="small"
color={
item.status === 'done' ? 'success' :
item.status === 'error' ? 'error' :
item.status === 'uploading' ? 'primary' : 'default'
}
icon={
item.status === 'done' ? <SuccessIcon /> :
item.status === 'error' ? <ErrorIcon /> : undefined
}
/>
</Box>
))}
</Paper>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowUploadDialog(false)} disabled={isUploading}>
Cancel
</Button>
<Button
onClick={startDirectoryUpload}
variant="contained"
disabled={isUploading || selectedFiles.length === 0}
>
{isUploading ? 'Uploading...' : 'Start Upload'}
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default SimpleUploadTest;