parent
39792b5dbe
commit
bd922c498f
6 changed files with 618 additions and 43 deletions
@ -0,0 +1,212 @@ |
||||
import { useQuery } from "@tanstack/react-query"; |
||||
import { useParams, Link, useNavigate } from "react-router-dom"; |
||||
import { fetchTalk } from "../api"; |
||||
|
||||
/** |
||||
* TalkView component displays a single talk with full details. |
||||
*
|
||||
* It shows: |
||||
* - Speaker information with photo |
||||
* - Talk metadata (date, debate type, etc.) |
||||
* - Full text of the speech |
||||
* - Links to related resources |
||||
*/ |
||||
export function TalkView() { |
||||
const { id } = useParams<{ id: string }>(); |
||||
const navigate = useNavigate(); |
||||
|
||||
const { data: talk, isLoading, error } = useQuery({ |
||||
queryKey: ["talk", id], |
||||
queryFn: () => fetchTalk(id!), |
||||
enabled: !!id, |
||||
}); |
||||
|
||||
if (isLoading) { |
||||
return ( |
||||
<div className="talk-view"> |
||||
<div className="panel"> |
||||
<p>Laddar anförande...</p> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
if (error) { |
||||
return ( |
||||
<div className="talk-view"> |
||||
<div className="panel error-banner"> |
||||
Kunde inte ladda anförande: {error.message} |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
if (!talk) { |
||||
return ( |
||||
<div className="talk-view"> |
||||
<div className="panel"> |
||||
<p>Anförande hittades inte.</p> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
// Fix image URLs from http to https
|
||||
const imageUrl = talk.person?.bild_url_192?.replace('http://', 'https://'); |
||||
|
||||
const previousTalk = talk.navigation?.previous ?? null; |
||||
const nextTalk = talk.navigation?.next ?? null; |
||||
|
||||
|
||||
// Normalise database IDs so router links stay consistent.
|
||||
const normalizeTalkId = (rawId?: string | null): string | null => { |
||||
if (!rawId) { |
||||
return null; |
||||
} |
||||
// Handle both object with _id property and plain string
|
||||
const idString = typeof rawId === 'object' && rawId !== null && '_id' in rawId
|
||||
? (rawId as any)._id
|
||||
: String(rawId); |
||||
const result = idString.startsWith("talks/") ? idString.slice(6) : idString; |
||||
return result; |
||||
}; |
||||
const previousId = normalizeTalkId(previousTalk); |
||||
const nextId = normalizeTalkId(nextTalk); |
||||
|
||||
// Use browser history when possible so the search page restores prior state.
|
||||
const handleBackClick = () => { |
||||
navigate("/"); |
||||
}; |
||||
|
||||
return ( |
||||
<div className="talk-view"> |
||||
{/* Navigation row: previous, back, next */} |
||||
<div className="talk-view__navRow"> |
||||
{/* Left: Föregående tal */} |
||||
<div className="talk-view__navCell talk-view__navCell--left talk-view__navCell--minwidth"> |
||||
{previousId && previousTalk ? ( |
||||
<Link to={`/talk/${previousId}`} className="secondary-button talk-view__navButton"> |
||||
{/* Use fontSize 1em for arrow so button height matches others */} |
||||
<span aria-hidden="true" style={{ fontSize: "1em", marginRight: "0.3em" }}>←</span> |
||||
Föregående i protokollet |
||||
</Link> |
||||
) : ( |
||||
<span className="secondary-button talk-view__navButton" style={{ visibility: "hidden" }}> </span> |
||||
)} |
||||
</div> |
||||
{/* Center: Tillbaka till sökresultat */} |
||||
<div className="talk-view__navCell talk-view__navCell--center talk-view__navCell--minwidth"> |
||||
<button |
||||
type="button" |
||||
className="secondary-button talk-view__navButton" |
||||
onClick={handleBackClick} |
||||
> |
||||
Tillbaka till sökresultat |
||||
</button> |
||||
</div> |
||||
{/* Right: Nästa tal */} |
||||
<div className="talk-view__navCell talk-view__navCell--right talk-view__navCell--minwidth"> |
||||
{nextId && nextTalk ? ( |
||||
<Link to={`/talk/${nextId}`} className="secondary-button talk-view__navButton"> |
||||
Nästa i protokollet |
||||
{/* Use fontSize 1em for arrow so button height matches others */} |
||||
<span aria-hidden="true" style={{ fontSize: "1em", marginLeft: "0.3em" }}>→</span> |
||||
</Link> |
||||
) : ( |
||||
<span className="secondary-button talk-view__navButton" style={{ visibility: "hidden" }}> </span> |
||||
)} |
||||
</div> |
||||
</div> |
||||
|
||||
{/* Speaker card */} |
||||
<div className="panel talk-view__speaker"> |
||||
{imageUrl && ( |
||||
<img
|
||||
src={imageUrl}
|
||||
alt={talk.talare} |
||||
className="talk-view__speaker-photo" |
||||
/> |
||||
)} |
||||
<div className="talk-view__speaker-info"> |
||||
<div className="talk-view__speaker-row"> |
||||
<h1>{talk.talare}</h1> |
||||
<span className="party-chip" data-party={talk.parti ?? ""}> |
||||
{talk.parti} |
||||
</span> |
||||
</div> |
||||
<div className="talk-view__speaker-meta"> |
||||
{/* Show valkrets if available */} |
||||
{talk.person?.valkrets && ( |
||||
<span className="talk-view__speaker-detail"> |
||||
{talk.person.valkrets} |
||||
</span> |
||||
)} |
||||
{/* Show status if available */} |
||||
{talk.person?.status && ( |
||||
<span className="talk-view__speaker-detail"> |
||||
{talk.person.status} |
||||
</span> |
||||
)} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
{/* Talk metadata */} |
||||
<div className="panel talk-view__metadata"> |
||||
<dl className="talk-view__meta-grid"> |
||||
<dt>Datum</dt> |
||||
<dd>{talk.datum}</dd> |
||||
|
||||
<dt>Debattyp</dt> |
||||
<dd>{talk.kammaraktivitet}</dd> |
||||
|
||||
<dt>Rubrik</dt> |
||||
<dd>{talk.avsnittsrubrik}</dd> |
||||
|
||||
{talk.titel && ( |
||||
<> |
||||
<dt>Protokoll</dt> |
||||
<dd>{talk.titel}</dd> |
||||
</> |
||||
)} |
||||
</dl> |
||||
</div> |
||||
|
||||
{/* Talk text */} |
||||
<div className="panel talk-view__text"> |
||||
<div className="talk-view__content"> |
||||
{talk.anforandetext} |
||||
</div> |
||||
</div> |
||||
|
||||
{/* Links */} |
||||
{(talk.url_session || talk.url_audio) && ( |
||||
<div className="panel talk-view__links"> |
||||
<h3>Länkar</h3> |
||||
<div className="talk-view__link-group"> |
||||
{talk.url_session && ( |
||||
<a
|
||||
href={talk.url_session}
|
||||
target="_blank"
|
||||
rel="noreferrer" |
||||
className="primary" |
||||
> |
||||
Webb-TV → |
||||
</a> |
||||
)} |
||||
{talk.url_audio && ( |
||||
<a
|
||||
href={talk.url_audio}
|
||||
target="_blank"
|
||||
rel="noreferrer" |
||||
className="primary" |
||||
> |
||||
Ljud → |
||||
</a> |
||||
)} |
||||
</div> |
||||
</div> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
Loading…
Reference in new issue