import { useEffect, useMemo, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { Button, Flex, Skeleton, Typography } from 'antd' import { ArrowLeftOutlined } from '@ant-design/icons' import type { AuthorDetail as IAuthorDetail, Book } from '../../types' import { fetchAuthor } from '../../api/authors' import BookCard from '../../components/BookCard/BookCard' import s from './AuthorDetail.module.css' export default function AuthorDetail() { const { id } = useParams<{ id: string }>() const navigate = useNavigate() const [author, setAuthor] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { if (!id) return setLoading(true) fetchAuthor(Number(id)) .then(setAuthor) .catch(() => setAuthor(null)) .finally(() => setLoading(false)) }, [id]) // Group books: series → Map, standalone const seriesGroups = useMemo(() => { if (!author) return [] const map = new Map() for (const book of author.books) { if (book.series) { const arr = map.get(book.series.name) ?? [] arr.push(book) map.set(book.series.name, arr) } } for (const [, books] of map) books.sort((a, b) => a.series!.position - b.series!.position) return [...map.entries()].sort((a, b) => a[0].localeCompare(b[0])) }, [author]) const standalone = useMemo(() => author?.books .filter(b => !b.series) .sort((a, b) => a.title.localeCompare(b.title)) ?? [], [author]) const initials = author?.name.split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase() ?? '' const color = avatarColor(author?.id ?? 0) return (
{/* Back */}
{loading && } {!loading && !author && ( Author not found. )} {!loading && author && ( <> {/* Author header */}
{author.imageUrl ? {author.name} : {initials} }
{author.name} {author.books.length} {author.books.length === 1 ? 'book' : 'books'} {author.bornYear ? ` · Born ${author.bornYear}` : ''} {author.bio && ( {author.bio} )}
{/* Collections / series */} {seriesGroups.length > 0 && (
Collections {seriesGroups.map(([seriesName, books]) => (
{seriesName} {books.length} {books.length === 1 ? 'book' : 'books'}
{books.map(book => ( navigate(`/books/${b.id}`)} /> ))}
))}
)} {/* Standalone books */} {standalone.length > 0 && (
Standalone {standalone.length} {standalone.length === 1 ? 'book' : 'books'}
{standalone.map(book => ( navigate(`/books/${b.id}`)} /> ))}
)} )}
) } function avatarColor(id: number): string { const palette = ['#6750A4', '#7B5EA7', '#5E35B1', '#4527A0', '#9575CD', '#7E57C2'] return palette[id % palette.length] } function AuthorSkeleton() { return (
) }