Project scaffolding
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import type { Book } from '../../types'
|
||||
import { fetchBooks, updateBook } from '../../api/books'
|
||||
import MetadataForm from '../../components/MetadataForm/MetadataForm'
|
||||
import s from './Metadata.module.css'
|
||||
|
||||
export default function Metadata() {
|
||||
const [books, setBooks] = useState<Book[]>([])
|
||||
const [selected, setSelected] = useState<Book | null>(null)
|
||||
const [query, setQuery] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
fetchBooks().then(list => {
|
||||
setBooks(list)
|
||||
if (list.length > 0) setSelected(list[0])
|
||||
})
|
||||
}, [])
|
||||
|
||||
const filtered = useMemo(() =>
|
||||
books.filter(b =>
|
||||
!query ||
|
||||
b.title.toLowerCase().includes(query.toLowerCase()) ||
|
||||
b.authors.some(a => a.name.toLowerCase().includes(query.toLowerCase()))
|
||||
), [books, query])
|
||||
|
||||
function handleSave(patch: Partial<Book>) {
|
||||
if (!selected) return
|
||||
updateBook(selected.id, patch).then(updated => {
|
||||
setBooks(bs => bs.map(b => b.id === updated.id ? updated : b))
|
||||
setSelected(updated)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.layout}>
|
||||
<aside className={s.list}>
|
||||
<div className={s.listHeader}>
|
||||
<div className={s.search}>
|
||||
<span className={`material-symbols-outlined ${s.searchIcon}`}>search</span>
|
||||
<input
|
||||
className={s.searchInput}
|
||||
type="search"
|
||||
placeholder="Filter books…"
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className={s.bookList}>
|
||||
{filtered.map(book => (
|
||||
<li
|
||||
key={book.id}
|
||||
className={`${s.bookItem} ${selected?.id === book.id ? s.bookItemActive : ''}`}
|
||||
onClick={() => setSelected(book)}
|
||||
>
|
||||
<div className={s.thumb} style={{ background: book.color }}>
|
||||
{book.title[0]}
|
||||
</div>
|
||||
<div className={s.bookMeta}>
|
||||
<span className={s.bookTitle}>{book.title}</span>
|
||||
<span className={s.bookAuthor}>{book.authors.map(a => a.name).join(', ')}</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<main className={s.editor}>
|
||||
{selected ? (
|
||||
<>
|
||||
<div className={s.editorHeader}>
|
||||
<div className={s.editorCover} style={{ background: selected.color }}>
|
||||
{selected.title[0]}
|
||||
</div>
|
||||
<div>
|
||||
<p className={s.editorTitle}>{selected.title}</p>
|
||||
<p className={s.editorAuthor}>{selected.authors.map(a => a.name).join(', ')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<MetadataForm key={selected.id} book={selected} onSave={handleSave} />
|
||||
</>
|
||||
) : (
|
||||
<div className={s.empty}>Select a book to edit metadata</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user