138 lines
5.2 KiB
TypeScript
138 lines
5.2 KiB
TypeScript
import { useState } from "react"
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
|
import { api } from "../lib/api"
|
|
import { EmptyState } from "../components/EmptyState"
|
|
import { LoadingSpinner } from "../components/LoadingSpinner"
|
|
|
|
export default function Documents() {
|
|
const queryClient = useQueryClient()
|
|
const [uploading, setUploading] = useState(false)
|
|
|
|
const { data: documents, isLoading, isError } = useQuery({
|
|
queryKey: ["documents"],
|
|
queryFn: () => api.listDocuments()
|
|
})
|
|
|
|
const uploadMutation = useMutation({
|
|
mutationFn: (file: File) => api.uploadDocument(file),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["documents"] })
|
|
setUploading(false)
|
|
},
|
|
onError: () => {
|
|
setUploading(false)
|
|
alert("Upload failed")
|
|
}
|
|
})
|
|
|
|
const deleteMutation = useMutation({
|
|
mutationFn: (id: string) => api.deleteDocument(id),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["documents"] })
|
|
}
|
|
})
|
|
|
|
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0]
|
|
if (!file) return
|
|
|
|
setUploading(true)
|
|
uploadMutation.mutate(file)
|
|
}
|
|
|
|
const formatSize = (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]
|
|
}
|
|
|
|
if (isError) return <div className="p-6 text-red-500">Error loading documents.</div>
|
|
|
|
return (
|
|
<div className="p-6 max-w-6xl mx-auto space-y-8">
|
|
<header>
|
|
<h1 className="text-2xl font-bold text-gray-900">Documents</h1>
|
|
<p className="text-gray-500">Store and manage your project files</p>
|
|
</header>
|
|
|
|
<section className="bg-white p-8 rounded-lg border-2 border-dashed border-gray-300 flex flex-col items-center justify-center space-y-4 hover:border-blue-400 transition-colors">
|
|
<div className="text-center">
|
|
<p className="text-lg font-medium text-gray-700">Upload a new document</p>
|
|
<p className="text-sm text-gray-500 mb-4">PDF, Images, or Text files</p>
|
|
</div>
|
|
|
|
<label className="relative cursor-pointer bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors font-medium">
|
|
{uploading ? "Uploading..." : "Select File"}
|
|
<input
|
|
type="file"
|
|
className="hidden"
|
|
onChange={handleFileUpload}
|
|
disabled={uploading}
|
|
/>
|
|
</label>
|
|
</section>
|
|
|
|
<section className="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
|
|
<table className="w-full text-left border-collapse">
|
|
<thead className="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th className="px-6 py-3 text-xs font-semibold text-gray-600 uppercase">Filename</th>
|
|
<th className="px-6 py-3 text-xs font-semibold text-gray-600 uppercase">Size</th>
|
|
<th className="px-6 py-3 text-xs font-semibold text-gray-600 uppercase">Date</th>
|
|
<th className="px-6 py-3 text-xs font-semibold text-gray-600 uppercase text-right">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200">
|
|
{isLoading ? (
|
|
<tr>
|
|
<td colSpan={4} className="px-6 py-10 text-center">
|
|
<LoadingSpinner />
|
|
</td>
|
|
</tr>
|
|
) : documents && documents.length > 0 ? (
|
|
documents.map((doc) => (
|
|
<tr key={doc.id} className="hover:bg-gray-50 transition-colors">
|
|
<td className="px-6 py-4 text-sm font-medium text-gray-900 truncate max-w-xs">
|
|
{doc.filename}
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-gray-500">
|
|
{formatSize(doc.size)}
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-gray-500">
|
|
{new Date(doc.createdAt).toLocaleDateString()}
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-right space-x-3">
|
|
<button
|
|
onClick={() => window.open(`/api/documents/${doc.id}/file`)}
|
|
className="text-blue-600 hover:text-blue-800 font-medium"
|
|
>
|
|
Download
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
if (confirm("Delete this document?")) {
|
|
deleteMutation.mutate(doc.id)
|
|
}
|
|
}}
|
|
className="text-red-600 hover:text-red-800 font-medium"
|
|
>
|
|
Delete
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))
|
|
) : (
|
|
<tr>
|
|
<td colSpan={4} className="px-6 py-10">
|
|
<EmptyState message="No documents uploaded yet." />
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
</div>
|
|
)
|
|
} |