feat: add book transcripts categorisation
This commit is contained in:
@@ -24,6 +24,7 @@ import {
|
||||
|
||||
export type YearGroup = { year: number; count: number };
|
||||
export type PlaceGroup = { name: string; count: number };
|
||||
export type BookGroup = { code: string; title: string; count: number };
|
||||
|
||||
type SortMode = "count" | "asc" | "desc";
|
||||
|
||||
@@ -59,14 +60,14 @@ function normalize(text: string): string {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SORT_OPTIONS: { key: SortMode; label: string; icon: typeof Hash }[] = [
|
||||
{ key: "count", label: "Most transcriptions first", icon: Hash },
|
||||
{ key: "count", label: "Most transcripts first", icon: Hash },
|
||||
{ key: "asc", label: "A → Z", icon: ArrowDownAZ },
|
||||
{ key: "desc", label: "Z → A", icon: ArrowUpAZ },
|
||||
];
|
||||
|
||||
const SORT_OPTIONS_YEAR: { key: SortMode; label: string; icon: typeof Hash }[] =
|
||||
[
|
||||
{ key: "count", label: "Most transcriptions first", icon: Hash },
|
||||
{ key: "count", label: "Most transcripts first", icon: Hash },
|
||||
{ key: "asc", label: "Oldest first", icon: ArrowDownAZ },
|
||||
{ key: "desc", label: "Newest first", icon: ArrowUpAZ },
|
||||
];
|
||||
@@ -150,6 +151,7 @@ function SortPopover({
|
||||
|
||||
const PAGE_SIZE_PLACES = 60;
|
||||
const PAGE_SIZE_YEARS = 100;
|
||||
const PAGE_SIZE_BOOKS = 100;
|
||||
|
||||
function Pagination({
|
||||
page,
|
||||
@@ -227,6 +229,18 @@ function sortPlaces(data: PlaceGroup[], mode: SortMode): PlaceGroup[] {
|
||||
}
|
||||
}
|
||||
|
||||
function sortBooks(data: BookGroup[], mode: SortMode): BookGroup[] {
|
||||
const sorted = [...data];
|
||||
switch (mode) {
|
||||
case "count":
|
||||
return sorted.sort((a, b) => b.count - a.count);
|
||||
case "asc":
|
||||
return sorted.sort((a, b) => a.title.localeCompare(b.title));
|
||||
case "desc":
|
||||
return sorted.sort((a, b) => b.title.localeCompare(a.title));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Component
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -235,12 +249,14 @@ export function TranscriptionsBrowser({
|
||||
currentView,
|
||||
years,
|
||||
places,
|
||||
books,
|
||||
lectureCount,
|
||||
conversationCount,
|
||||
}: {
|
||||
currentView: string;
|
||||
years: YearGroup[];
|
||||
places: PlaceGroup[];
|
||||
books: BookGroup[];
|
||||
lectureCount: number;
|
||||
conversationCount: number;
|
||||
}) {
|
||||
@@ -248,6 +264,7 @@ export function TranscriptionsBrowser({
|
||||
const [page, setPage] = useState(1);
|
||||
const [sortYear, setSortYear] = useState<SortMode>("asc");
|
||||
const [sortPlace, setSortPlace] = useState<SortMode>("count");
|
||||
const [sortBook, setSortBook] = useState<SortMode>("count");
|
||||
|
||||
const handleQueryChange = (q: string) => {
|
||||
setQuery(q);
|
||||
@@ -256,11 +273,17 @@ export function TranscriptionsBrowser({
|
||||
|
||||
const handleSortChange = (mode: SortMode) => {
|
||||
if (currentView === "year") setSortYear(mode);
|
||||
else if (currentView === "book") setSortBook(mode);
|
||||
else setSortPlace(mode);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const currentSort = currentView === "year" ? sortYear : sortPlace;
|
||||
const currentSort =
|
||||
currentView === "year"
|
||||
? sortYear
|
||||
: currentView === "book"
|
||||
? sortBook
|
||||
: sortPlace;
|
||||
|
||||
// --- Filtered + sorted data ---
|
||||
|
||||
@@ -282,13 +305,30 @@ export function TranscriptionsBrowser({
|
||||
return sortPlaces(data, sortPlace);
|
||||
}, [places, query, sortPlace]);
|
||||
|
||||
const filteredBooks = useMemo(() => {
|
||||
let data = books;
|
||||
if (query.trim()) {
|
||||
const q = normalize(query.trim());
|
||||
data = data.filter((bg) => normalize(bg.title).includes(q));
|
||||
}
|
||||
return sortBooks(data, sortBook);
|
||||
}, [books, query, sortBook]);
|
||||
|
||||
// --- Paginated data ---
|
||||
|
||||
const activeData =
|
||||
currentView === "year" ? filteredYears : filteredPlaces;
|
||||
currentView === "year"
|
||||
? filteredYears
|
||||
: currentView === "book"
|
||||
? filteredBooks
|
||||
: filteredPlaces;
|
||||
|
||||
const pageSize =
|
||||
currentView === "year" ? PAGE_SIZE_YEARS : PAGE_SIZE_PLACES;
|
||||
currentView === "year"
|
||||
? PAGE_SIZE_YEARS
|
||||
: currentView === "book"
|
||||
? PAGE_SIZE_BOOKS
|
||||
: PAGE_SIZE_PLACES;
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(activeData.length / pageSize));
|
||||
const safePage = Math.min(page, totalPages);
|
||||
@@ -296,11 +336,26 @@ export function TranscriptionsBrowser({
|
||||
const endIdx = startIdx + pageSize;
|
||||
|
||||
const placeholder =
|
||||
currentView === "year" ? "Search years..." : "Search places...";
|
||||
currentView === "year"
|
||||
? "Search years..."
|
||||
: currentView === "book"
|
||||
? "Search books..."
|
||||
: "Search places...";
|
||||
|
||||
const resultCount = activeData.length;
|
||||
const totalCount =
|
||||
currentView === "year" ? years.length : places.length;
|
||||
currentView === "year"
|
||||
? years.length
|
||||
: currentView === "book"
|
||||
? books.length
|
||||
: places.length;
|
||||
|
||||
const noun =
|
||||
currentView === "year"
|
||||
? "years"
|
||||
: currentView === "book"
|
||||
? "books"
|
||||
: "places";
|
||||
|
||||
// --- Type view (no search/sort/pagination needed) ---
|
||||
|
||||
@@ -368,7 +423,7 @@ export function TranscriptionsBrowser({
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{resultCount === 0
|
||||
? "No results found"
|
||||
: `${resultCount} of ${totalCount} ${currentView === "year" ? "years" : "places"}`}
|
||||
: `${resultCount} of ${totalCount} ${noun}`}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -415,6 +470,30 @@ export function TranscriptionsBrowser({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Book view */}
|
||||
{currentView === "book" && (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||
{(filteredBooks.slice(startIdx, endIdx) as BookGroup[]).map((bg) => (
|
||||
<Link
|
||||
key={bg.code}
|
||||
href={`/transcriptions/book/${bg.code}`}
|
||||
>
|
||||
<Card className="transition-all hover:shadow-md hover:ring-foreground/20">
|
||||
<CardContent className="py-6 px-4 text-center">
|
||||
<p className="font-semibold text-foreground leading-tight">
|
||||
{bg.title}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{bg.count}{" "}
|
||||
{bg.count === 1 ? "transcription" : "transcriptions"}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
<Pagination
|
||||
page={safePage}
|
||||
|
||||
Reference in New Issue
Block a user