web: more details in search results
This commit is contained in:
@@ -3,17 +3,38 @@ import { useEffect, useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { PageHeader } from "../general/PageHeader";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { getSearchPath } from "@/lib/common";
|
||||
import styles from './searchContainer.module.scss';
|
||||
import {
|
||||
ImageFragmentDefinition,
|
||||
getSearchPath,
|
||||
stripHtml,
|
||||
} from "@/lib/common";
|
||||
import { formatDate, formatOccurrenceMonths } from "@/lib/date";
|
||||
import { unmaskFragment } from "@/gql";
|
||||
import type { ImageFragment, SearchQuery } from "@/gql/graphql";
|
||||
import styles from "./searchContainer.module.scss";
|
||||
import { Icon } from "../general/Icon";
|
||||
import { Image } from "../general/Image";
|
||||
import Link from "next/link";
|
||||
|
||||
export type SearchResult = SearchQuery["results"][number];
|
||||
|
||||
const PAGE_TYPES = {
|
||||
NewsPage: "Nyhet",
|
||||
EventPage: "Arrangement",
|
||||
GenericPage: "Underside",
|
||||
VenuePage: "Lokale",
|
||||
AssociationPage: "Forening",
|
||||
} as const;
|
||||
|
||||
type SupportedTypename = keyof typeof PAGE_TYPES;
|
||||
type SupportedResult = Extract<SearchResult, { __typename: SupportedTypename }>;
|
||||
|
||||
export function SearchContainer({
|
||||
query,
|
||||
results,
|
||||
}: {
|
||||
query: string;
|
||||
results: any;
|
||||
results: SearchResult[];
|
||||
}) {
|
||||
const { replace } = useRouter();
|
||||
const [inputValue, setInputValue] = useState(query);
|
||||
@@ -53,50 +74,109 @@ function capitalizeFirstLetter(s: string) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
function linkTo(page: any): string | null {
|
||||
return page?.url ?? null;
|
||||
function isSupported(result: SearchResult): result is SupportedResult {
|
||||
return (
|
||||
result.__typename in PAGE_TYPES &&
|
||||
"id" in result &&
|
||||
!!result.id
|
||||
);
|
||||
}
|
||||
|
||||
const PAGE_TYPES: Record<string, string> = {
|
||||
NewsPage: "Nyhet",
|
||||
EventPage: "Arrangement",
|
||||
GenericPage: "Underside",
|
||||
VenuePage: "Lokale",
|
||||
AssociationPage: "Forening",
|
||||
};
|
||||
function getResultType(result: SupportedResult): string {
|
||||
if (result.__typename === "AssociationPage" && result.associationType) {
|
||||
return capitalizeFirstLetter(result.associationType);
|
||||
}
|
||||
return PAGE_TYPES[result.__typename];
|
||||
}
|
||||
|
||||
function SearchResults({ results }: { results: any }) {
|
||||
function getResultImage(result: SupportedResult): ImageFragment | null {
|
||||
switch (result.__typename) {
|
||||
case "NewsPage":
|
||||
case "EventPage":
|
||||
case "VenuePage":
|
||||
return unmaskFragment(ImageFragmentDefinition, result.featuredImage);
|
||||
case "AssociationPage":
|
||||
return unmaskFragment(ImageFragmentDefinition, result.logo);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getResultDate(result: SupportedResult): string | null {
|
||||
if (result.__typename === "EventPage") {
|
||||
const starts = result.occurrences
|
||||
.map((o) => o.start)
|
||||
.filter((s): s is string => !!s);
|
||||
if (starts.length === 0) return null;
|
||||
if (starts.length === 1) return formatDate(starts[0], "d. MMMM yyyy");
|
||||
return formatOccurrenceMonths(starts);
|
||||
}
|
||||
if (result.__typename === "NewsPage" && result.firstPublishedAt) {
|
||||
return formatDate(result.firstPublishedAt, "d. MMMM yyyy");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getResultSnippet(result: SupportedResult): string | null {
|
||||
switch (result.__typename) {
|
||||
case "NewsPage":
|
||||
case "AssociationPage":
|
||||
return result.excerpt ?? null;
|
||||
case "EventPage":
|
||||
return result.subtitle ?? null;
|
||||
case "GenericPage":
|
||||
return result.lead ? stripHtml(result.lead).trim() : null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function SearchResults({ results }: { results: SearchResult[] }) {
|
||||
if (!results.length) {
|
||||
return <div className={styles.noResults}>Ingen resultater</div>;
|
||||
}
|
||||
const supportedResults = results.filter(
|
||||
(result: any) =>
|
||||
!!result?.id && Object.keys(PAGE_TYPES).includes(result.__typename)
|
||||
);
|
||||
const supportedResults = results.filter(isSupported);
|
||||
return (
|
||||
<div>
|
||||
<p className={styles.resultsCounter}>{results.length} resultater</p>
|
||||
{supportedResults.map((result: any) => {
|
||||
let resultType = PAGE_TYPES[result.__typename] ?? "";
|
||||
if (result.__typename === "AssociationPage") {
|
||||
resultType = capitalizeFirstLetter(result?.associationType);
|
||||
}
|
||||
const link = linkTo(result);
|
||||
const ResultItem = () => (
|
||||
<div className={styles.resultItem}>
|
||||
<span className={styles.suphead}>{resultType}</span>
|
||||
<h2 className={styles.title}>{result.title}</h2>
|
||||
</div>
|
||||
);
|
||||
if (link) {
|
||||
return (
|
||||
<Link key={result.id} href={link}>
|
||||
<ResultItem />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return <ResultItem key={result.id} />;
|
||||
})}
|
||||
{supportedResults.map((result) => (
|
||||
<ResultRow key={result.id} result={result} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ResultRow({ result }: { result: SupportedResult }) {
|
||||
const image = getResultImage(result);
|
||||
const snippet = getResultSnippet(result);
|
||||
const date = getResultDate(result);
|
||||
const resultType = getResultType(result);
|
||||
const link = result.url;
|
||||
|
||||
const body = (
|
||||
<div className={styles.resultItem}>
|
||||
<div className={styles.resultBody}>
|
||||
<span className={styles.suphead}>{resultType}</span>
|
||||
<h2 className={styles.title}>{result.title}</h2>
|
||||
{date && <p className={styles.date}>{date}</p>}
|
||||
{snippet && <p className={styles.snippet}>{snippet}</p>}
|
||||
</div>
|
||||
{image?.url && (
|
||||
<div className={styles.thumb}>
|
||||
<Image
|
||||
src={image.url}
|
||||
alt={image.alt ?? ""}
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
sizes="100px"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (link) {
|
||||
return <Link href={link}>{body}</Link>;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,42 @@
|
||||
}
|
||||
|
||||
.resultItem {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-m);
|
||||
border-top: var(--border);
|
||||
margin-top: var(--spacing-m);
|
||||
padding-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.resultBody {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-top: 0.25em;
|
||||
font-family: var(--font-serif);
|
||||
font-size: var(--font-size-caption);
|
||||
color: var(--color-chateauBlue-05);
|
||||
}
|
||||
|
||||
.snippet {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.thumb {
|
||||
flex: 0 0 5rem;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.suphead {
|
||||
display: block;
|
||||
font-size: var(--font-size-caption);
|
||||
@@ -48,4 +79,4 @@
|
||||
|
||||
.noResults {
|
||||
margin: var(--spacing-s) 0 var(--spacing-section-bottom);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user