Paperjet/frontend/src/features/library/LibraryHeader.tsx
Elijah eb8a302222
Some checks failed
Automated Container Build / build-and-push (push) Failing after 2s
CI / Backend (Python) (push) Failing after 9s
CI / Frontend (TypeScript) (push) Failing after 4m46s
Fix bugs, convert thumbnail to png, add context menu
2026-06-10 19:06:28 -07:00

169 lines
8 KiB
TypeScript

import { useLibrary } from './useLibrary'
import { useAuth } from '../auth/useAuth'
export const LibraryHeader = ({ currentTab, onTabChange }: { currentTab: 'library' | 'trash', onTabChange: (tab: 'library' | 'trash') => void }) => {
const { logout } = useAuth()
const {
searchQuery, setSearchQuery, fetchDocuments, fetchTrash,
selectedIds, clearSelection, selectAll, bulkDelete, bulkRestore, emptyTrash, documents
} = useLibrary()
const handleSearch = (e: React.FormEvent) => {
e.preventDefault()
if (currentTab === 'library') {
fetchDocuments(searchQuery)
}
}
const handleTabChange = (tab: 'library' | 'trash') => {
onTabChange(tab)
if (tab === 'library') {
fetchDocuments()
} else {
fetchTrash()
}
}
const isSelectionMode = selectedIds.size > 0
const allSelected = documents.length > 0 && selectedIds.size === documents.length
return (
<div className="sticky top-0 z-40 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-800">
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between gap-4">
{/* Left: Branding & Tabs */}
<div className="flex items-center gap-8">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center shadow-lg shadow-indigo-500/30">
<span className="text-white font-bold text-xl leading-none">P</span>
</div>
<h1 className="text-xl font-bold text-gray-900 dark:text-white hidden sm:block">PaperJet</h1>
</div>
<nav className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 p-1 rounded-lg">
<button
onClick={() => handleTabChange('library')}
className={`px-4 py-1.5 rounded-md text-sm font-medium transition-all ${
currentTab === 'library'
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
Library
</button>
<button
onClick={() => handleTabChange('trash')}
className={`px-4 py-1.5 rounded-md text-sm font-medium transition-all ${
currentTab === 'trash'
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
Trash
</button>
</nav>
</div>
{/* Right: Search & Actions */}
<div className="flex items-center gap-4 flex-1 justify-end">
{isSelectionMode ? (
<div className="flex items-center gap-3 bg-indigo-50 dark:bg-indigo-900/30 px-4 py-1.5 rounded-full border border-indigo-100 dark:border-indigo-800">
<span className="text-sm font-medium text-indigo-700 dark:text-indigo-300">
{selectedIds.size} selected
</span>
<div className="h-4 w-px bg-indigo-200 dark:bg-indigo-700"></div>
<button onClick={allSelected ? clearSelection : selectAll} className="text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-200 font-medium">
{allSelected ? 'Deselect All' : 'Select All'}
</button>
{currentTab === 'library' ? (
<button
onClick={() => bulkDelete()}
className="text-sm text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium px-2 py-1 hover:bg-red-50 dark:hover:bg-red-900/30 rounded"
>
Delete
</button>
) : (
<>
<button
onClick={() => bulkRestore()}
className="text-sm text-green-600 dark:text-green-400 hover:text-green-800 dark:hover:text-green-300 font-medium px-2 py-1 hover:bg-green-50 dark:hover:bg-green-900/30 rounded"
>
Restore
</button>
<button
onClick={() => bulkDelete(true)}
className="text-sm text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium px-2 py-1 hover:bg-red-50 dark:hover:bg-red-900/30 rounded"
>
Delete Forever
</button>
</>
)}
</div>
) : (
<>
{currentTab === 'library' && (
<form onSubmit={handleSearch} className="relative hidden md:block max-w-md w-full">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search documents..."
className="block w-full pl-10 pr-3 py-2 border border-gray-200 dark:border-gray-700 rounded-xl leading-5 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent sm:text-sm transition-all"
/>
</form>
)}
{currentTab === 'trash' && (
<button
onClick={() => emptyTrash()}
className="text-sm font-medium text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 transition-colors"
>
Empty Trash
</button>
)}
{currentTab === 'library' && (
<label className="cursor-pointer inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-xl shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-all active:scale-95">
<span>Upload PDF</span>
<input
type="file"
accept="application/pdf"
multiple
className="hidden"
onChange={(e) => {
// We'll dispatch a custom event that UploadArea listens to, or just click its input
// For simplicity, find the hidden file input in UploadArea and click it
const uploader = document.querySelector('input[type="file"][multiple]') as HTMLInputElement
if (uploader && uploader !== e.target) {
uploader.files = e.target.files
const event = new Event('change', { bubbles: true })
uploader.dispatchEvent(event)
}
}}
/>
</label>
)}
<button
onClick={logout}
className="p-2 text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
title="Logout"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</button>
</>
)}
</div>
</div>
</div>
)
}