diff --git a/app/[locale]/works/[category]/page.tsx b/app/[locale]/works/[category]/[id]/page.tsx similarity index 56% rename from app/[locale]/works/[category]/page.tsx rename to app/[locale]/works/[category]/[id]/page.tsx index 957d4f6..2741a93 100644 --- a/app/[locale]/works/[category]/page.tsx +++ b/app/[locale]/works/[category]/[id]/page.tsx @@ -1,55 +1,87 @@ "use client"; import type { RefinementListItem } from "instantsearch.js/es/connectors/refinement-list/connectRefinementList"; import { useTranslations } from "next-intl"; +import { useEffect, useState } from "react"; import { Results } from "@/components/instantsearch/results"; import { SingleRefinementDropdown } from "@/components/instantsearch/single-refinement-dropdown"; import { SingleRefinementList } from "@/components/instantsearch/single-refinement-list"; import { ThomasBernhardInstantSearchProvider } from "@/components/instantsearch/thomas-bernhard/thomasbernhard-instantsearchprovider"; import { MainContent } from "@/components/main-content"; +import { getWorks } from "@/lib/data"; +import type { BernhardWork, Category } from "@/lib/model"; interface WorksPageProps { params: { category: string; + id?: string; }; } export default function WorksPage(props: WorksPageProps) { - const ct = useTranslations("BernhardCategories"); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const categoryLabel = ct(props.params.category as any); - // TODO validate category const t = useTranslations("InstantSearch"); + const ct = useTranslations("BernhardCategories"); const tl = useTranslations("Languages"); - // TODO get id -> year+title dictionaries from backend + // TODO validate category + const category = props.params.category; + const categoryLabel = ct(category as Category); + + // get id -> work info dictionary once on pageload + const [works, setWorks] = useState({} as Record); + useEffect(() => { + const get = async () => { + setWorks(await getWorks(category as Category)); + }; + // eslint-disable-next-line @typescript-eslint/no-floating-promises + get(); + }, [category]); return (
) => { - return items - .filter((item) => { - return item.label.startsWith(props.params.category); - }) - .map((item) => { - const [_category, year, title] = item.label.split("_"); - item.label = Number.isNaN(parseInt(year!)) ? title! : `${title!} (${year!})`; - return item; - }); + return ( + items + // the refinement may contain out-of-category works which are contained in + // publications which also contain works of this category (and therefore show up + // in the filtered search) + .filter((item) => { + return item.value in works; + }) + .sort((a, b) => { + const ya = works[a.value]!.year; + const yb = works[b.value]!.year; + if (!ya) { + return 1; + } else if (!yb) { + return -1; + } else { + return ya - yb; + } + }) + .map((item) => { + const work = works[item.value]!; + item.label = work.short_title ?? work.title; + if (work.year) { + item.label += ` (${work.year.toString()})`; + } + return item; + }) + ); }, }} /> diff --git a/components/bernhard-links.tsx b/components/bernhard-links.tsx index f562aea..521a2cf 100644 --- a/components/bernhard-links.tsx +++ b/components/bernhard-links.tsx @@ -10,11 +10,7 @@ interface BernhardWorkProps { export function BernhardWorkLink(props: BernhardWorkProps) { return ( - + {props.display_title ?? props.work.title} ); diff --git a/components/instantsearch-view.tsx b/components/instantsearch-view.tsx index 8f25921..4ebb0ad 100644 --- a/components/instantsearch-view.tsx +++ b/components/instantsearch-view.tsx @@ -1,6 +1,12 @@ "use client"; -import { LayoutGrid, LayoutList } from "lucide-react"; +import { + ArrowDownAZ, + CalendarArrowDown, + CalendarArrowUp, + LayoutGrid, + LayoutList, +} from "lucide-react"; import { useTranslations } from "next-intl"; import { type ReactNode, useState } from "react"; import { Switch } from "react-aria-components"; @@ -21,8 +27,8 @@ interface InstantSearchViewProps extends Partial - + {props.children} ; }); } + +export async function getWorks(category: Category): Promise> { + const x = await collection.documents().search({ + q: "*", + filter_by: `contains.work.category:${category}`, + per_page: 250, // hard maximum + }); + const works = x.hits?.reduce((accumulator: Record, hit) => { + const pub = hit.document as Publication; + pub.contains + .filter((translation) => { + return translation.work.category === category; + }) + .forEach((translation) => { + if (!(translation.work.id in accumulator)) { + accumulator[translation.work.id] = translation.work; + } + }); + return accumulator; + }, {}); + return works!; +} diff --git a/scripts/3_to_typesense.py b/scripts/3_to_typesense.py index 51851f3..2d86edf 100755 --- a/scripts/3_to_typesense.py +++ b/scripts/3_to_typesense.py @@ -136,10 +136,6 @@ def del_empty_strings(o, field_names): w["category"] = categories[w["category"]] if w["category"] else "fragments" - # helper field for the faceted listing by work - # w["yeartitle"] = f'{w["category"]}_{w["year"]}_{w["short_title"].replace(" ", "_")}' - w["yeartitle"] = f'{w["category"]}_{w["year"]}_{w["short_title"]}' - for t in translations: t["work"] = works[t["work"] - 1] t["translators"] = [translators[t_id - 1] for t_id in t["translators"]]