Spaces:
Running
Running
| import React, { useEffect, useState } from 'react'; | |
| import { FileText, Download, Loader, Trash2, AlertCircle } from 'lucide-react'; | |
| interface FileListProps { | |
| supabase: any; | |
| darkMode: boolean; | |
| isAdmin?: boolean; | |
| } | |
| interface FileData { | |
| id: string; | |
| filename: string; | |
| storage_path: string; | |
| file_type: string; | |
| uploaded_at: string; | |
| } | |
| const FileList: React.FC<FileListProps> = ({ supabase, darkMode, isAdmin = false }) => { | |
| const [files, setFiles] = useState<FileData[]>([]); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| const [downloading, setDownloading] = useState<string | null>(null); | |
| const [deleting, setDeleting] = useState<string | null>(null); | |
| useEffect(() => { | |
| fetchFiles(); | |
| }, []); | |
| const fetchFiles = async () => { | |
| try { | |
| setError(null); | |
| setLoading(true); | |
| // Check if Supabase is initialized properly | |
| if (!supabase) { | |
| throw new Error('Database connection not initialized'); | |
| } | |
| // Test connection with a simple query first | |
| const { error: connectionError } = await supabase | |
| .from('codette_files') | |
| .select('count'); | |
| if (connectionError) { | |
| throw connectionError; | |
| } | |
| // Proceed with actual data fetch | |
| const { data, error } = await supabase | |
| .from('codette_files') | |
| .select('*') | |
| .order('uploaded_at', { ascending: false }); | |
| if (error) throw error; | |
| setFiles(data || []); | |
| } catch (err: any) { | |
| console.error('Error fetching files:', err); | |
| setError(err.message || 'Failed to fetch files. Please check your connection.'); | |
| setFiles([]); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const handleDownload = async (file: FileData) => { | |
| try { | |
| setDownloading(file.id); | |
| setError(null); | |
| const { data, error } = await supabase.storage | |
| .from('codette-files') | |
| .download(file.storage_path); | |
| if (error) throw error; | |
| const url = window.URL.createObjectURL(data); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = file.filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| window.URL.revokeObjectURL(url); | |
| document.body.removeChild(a); | |
| } catch (err: any) { | |
| console.error('Error downloading file:', err); | |
| setError(err.message || 'Failed to download file. Please try again.'); | |
| } finally { | |
| setDownloading(null); | |
| } | |
| }; | |
| const handleDelete = async (file: FileData) => { | |
| if (!isAdmin) return; | |
| if (!confirm('Are you sure you want to delete this file?')) return; | |
| try { | |
| setDeleting(file.id); | |
| setError(null); | |
| // Delete from storage | |
| const { error: storageError } = await supabase.storage | |
| .from('codette-files') | |
| .remove([file.storage_path]); | |
| if (storageError) throw storageError; | |
| // Delete from database | |
| const { error: dbError } = await supabase | |
| .from('codette_files') | |
| .delete() | |
| .match({ id: file.id }); | |
| if (dbError) throw dbError; | |
| // Update local state | |
| setFiles(files.filter(f => f.id !== file.id)); | |
| } catch (err: any) { | |
| console.error('Error deleting file:', err); | |
| setError(err.message || 'Failed to delete file. Please try again.'); | |
| } finally { | |
| setDeleting(null); | |
| } | |
| }; | |
| const handleRetry = () => { | |
| fetchFiles(); | |
| }; | |
| if (loading) { | |
| return ( | |
| <div className="flex items-center justify-center p-4"> | |
| <Loader className="animate-spin" size={24} /> | |
| </div> | |
| ); | |
| } | |
| if (error) { | |
| return ( | |
| <div className={`p-4 rounded-lg ${darkMode ? 'bg-red-900/20' : 'bg-red-50'}`}> | |
| <div className="flex items-start space-x-2"> | |
| <AlertCircle className={`flex-shrink-0 ${darkMode ? 'text-red-400' : 'text-red-500'}`} size={20} /> | |
| <div className="flex-1"> | |
| <p className={`text-sm font-medium ${darkMode ? 'text-red-400' : 'text-red-800'}`}> | |
| Connection Error | |
| </p> | |
| <p className={`text-sm mt-1 ${darkMode ? 'text-red-300' : 'text-red-600'}`}> | |
| {error} | |
| </p> | |
| <button | |
| onClick={handleRetry} | |
| className={`mt-3 px-3 py-1 rounded-md text-sm ${ | |
| darkMode | |
| ? 'bg-red-900/30 hover:bg-red-900/50 text-red-300' | |
| : 'bg-red-100 hover:bg-red-200 text-red-700' | |
| }`} | |
| > | |
| Try Again | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="space-y-2"> | |
| <h3 className="text-sm font-semibold mb-3">Uploaded Files</h3> | |
| {files.length === 0 ? ( | |
| <p className={`text-sm ${darkMode ? 'text-gray-400' : 'text-gray-500'}`}> | |
| No files uploaded yet. | |
| </p> | |
| ) : ( | |
| <div className="space-y-2"> | |
| {files.map((file) => ( | |
| <div | |
| key={file.id} | |
| className={`p-3 rounded-md ${ | |
| darkMode ? 'bg-gray-700 hover:bg-gray-600' : 'bg-gray-100 hover:bg-gray-200' | |
| } transition-colors flex items-center justify-between`} | |
| > | |
| <div className="flex items-center space-x-2"> | |
| <FileText size={16} className="text-blue-500" /> | |
| <div> | |
| <p className="text-sm font-medium truncate max-w-[150px]"> | |
| {file.filename} | |
| </p> | |
| <p className={`text-xs ${darkMode ? 'text-gray-400' : 'text-gray-500'}`}> | |
| {new Date(file.uploaded_at).toLocaleDateString()} | |
| </p> | |
| </div> | |
| </div> | |
| <div className="flex items-center space-x-2"> | |
| <button | |
| onClick={() => handleDownload(file)} | |
| disabled={downloading === file.id} | |
| className={`p-1 rounded-md transition-colors ${ | |
| darkMode | |
| ? 'hover:bg-gray-500 text-gray-300' | |
| : 'hover:bg-gray-300 text-gray-700' | |
| }`} | |
| > | |
| {downloading === file.id ? ( | |
| <Loader className="animate-spin" size={16} /> | |
| ) : ( | |
| <Download size={16} /> | |
| )} | |
| </button> | |
| {isAdmin && ( | |
| <button | |
| onClick={() => handleDelete(file)} | |
| disabled={deleting === file.id} | |
| className={`p-1 rounded-md transition-colors ${ | |
| darkMode | |
| ? 'hover:bg-red-500 text-gray-300' | |
| : 'hover:bg-red-100 text-red-600' | |
| }`} | |
| > | |
| {deleting === file.id ? ( | |
| <Loader className="animate-spin" size={16} /> | |
| ) : ( | |
| <Trash2 size={16} /> | |
| )} | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default FileList; |