import { useState, useRef } from 'react';
import { Link } from 'react-router-dom';
import { useGetDocuments, useCreateDocument } from '../generated/anthoLumeAPIV1';
import type { Document } from '../generated/model';
import { ActivityIcon, DownloadIcon, Search2Icon, UploadIcon } from '../icons';
import { LoadingState, Pagination, TextInput, IconInput, SegmentedControl } from '../components';
import { useToasts } from '../components/ToastContext';
import { useMutationWithToast } from '../hooks/useMutationWithToast';
import { formatDuration } from '../utils/formatters';
import { cn } from '../utils/cn';
import { useDebouncedState } from '../hooks/useDebouncedState';
import { usePaginatedList } from '../hooks/usePaginatedList';
import { useLocalSetting, type DocumentsViewMode } from '../utils/localSettings';
const DOCUMENTS_PAGE_SIZE = 9;
interface DocumentItemProps {
doc: Document;
layout: 'grid' | 'list';
}
function DocumentItem({ doc, layout }: DocumentItemProps) {
const percentage = doc.percentage || 0;
const totalTimeSeconds = doc.total_time_seconds || 0;
const documentPath = `/documents/${doc.id}`;
const title = doc.title || 'Unknown';
const actions = (
{doc.filepath ? (
) : (
)}
);
const fields = [
{ label: 'Title', value: title, link: true },
{ label: 'Author', value: doc.author || 'Unknown' },
{ label: 'Progress', value: `${percentage}%` },
{ label: 'Time Read', value: formatDuration(totalTimeSeconds) },
];
const fieldList = (
{fields.map(field => (
{field.label}
{field.link ? (
{field.value}
) : (
{field.value}
)}
))}
);
if (layout === 'grid') {
return (
{fieldList}
{actions}
);
}
return (
);
}
function EmptyDocuments({ className }: { className?: string }) {
return (
No documents found.
);
}
export default function DocumentsPage() {
const [search, setSearch, debouncedSearch] = useDebouncedState('', 300);
const { page, setPage } = usePaginatedList(debouncedSearch);
const limit = DOCUMENTS_PAGE_SIZE;
const [uploadMode, setUploadMode] = useState(false);
const [viewMode, setViewMode] = useLocalSetting('documentsViewMode', 'grid');
const fileInputRef = useRef(null);
const { showWarning } = useToasts();
const toastMutationOptions = useMutationWithToast();
const { data, isLoading, refetch } = useGetDocuments({ page, limit, search: debouncedSearch });
const createMutation = useCreateDocument();
const documentsResponse = data;
const docs = documentsResponse?.documents;
const previousPage = documentsResponse?.previous_page;
const nextPage = documentsResponse?.next_page;
const uploadDocument = (file: File | undefined) => {
if (!file) return;
if (!file.name.endsWith('.epub')) {
showWarning('Please upload an EPUB file');
return;
}
createMutation.mutate(
{ data: { document_file: file } },
toastMutationOptions({
success: 'Document uploaded successfully!',
error: 'Failed to upload document',
onSuccess: () => {
setUploadMode(false);
refetch();
},
})
);
};
const handleCancelUpload = () => {
setUploadMode(false);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
return (
}>
setSearch(e.target.value)}
placeholder="Search Author / Title"
name="search"
/>
ariaLabel="Document view mode"
value={viewMode}
onChange={setViewMode}
options={[
{ value: 'grid', label: 'Grid' },
{ value: 'list', label: 'List' },
]}
/>
{viewMode === 'grid' ? (
{isLoading ? (
) : docs && docs.length > 0 ? (
docs.map(doc => )
) : (
)}
) : (
{isLoading ? (
) : docs && docs.length > 0 ? (
docs.map(doc => )
) : (
)}
)}
{uploadMode && (
)}
);
}