feat: add book transcripts categorisation

This commit is contained in:
gosangam
2026-06-17 12:40:40 +05:30
parent f96e7dd2f1
commit f5abe33c09
7 changed files with 930 additions and 10 deletions
@@ -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}