Studio lot 8e : lecteur audio dans la fiche d'un point de carte

Audio résolu par identifiant de ressource, portrait et nom du narrateur au-dessus du lecteur.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011VxSQeGQYUvPmSEoGdnidA
This commit is contained in:
Thomas Fransolet 2026-09-15 23:00:45 +02:00
parent 81c807cd77
commit d371b447c6
2 changed files with 22 additions and 2 deletions

View File

@ -13,6 +13,8 @@ import { t, tPlain } from '@/lib/i18n'
import type { SectionDTO, GeoPointDTO } from '@/lib/api/types' import type { SectionDTO, GeoPointDTO } from '@/lib/api/types'
import { trackEvent } from '@/lib/stats' import { trackEvent } from '@/lib/stats'
import { getGeoPointLatLng } from '@/lib/geo' import { getGeoPointLatLng } from '@/lib/geo'
import NarratorAvatar from '@/components/ui/NarratorAvatar'
import { useAudioResource } from '@/hooks/useAudioResource'
import './map/map.css' import './map/map.css'
const LeafletMap = dynamic(() => import('./map/LeafletMap'), { const LeafletMap = dynamic(() => import('./map/LeafletMap'), {
@ -29,6 +31,7 @@ interface Props {
slug: string slug: string
configId: string configId: string
languages: string[] languages: string[]
apiKey: string
} }
type Mode = 'map' | 'list' type Mode = 'map' | 'list'
@ -39,7 +42,7 @@ const DOCK_WIDTH = 340
const DETAIL_WIDTH = 320 const DETAIL_WIDTH = 320
const PANEL_TOP = 60 const PANEL_TOP = 60
export default function MapSection({ section, configId, languages }: Props) { export default function MapSection({ section, configId, languages, apiKey }: Props) {
const { language, setAvailableLanguages, instanceId } = useVisitor() const { language, setAvailableLanguages, instanceId } = useVisitor()
const back = useBack() const back = useBack()
@ -322,6 +325,7 @@ export default function MapSection({ section, configId, languages }: Props) {
{/* Detail sheet (POI) */} {/* Detail sheet (POI) */}
{selected && ( {selected && (
<PointDetail <PointDetail
apiKey={apiKey}
point={selected} point={selected}
language={language} language={language}
variant={isLandscape ? 'card' : 'sheet'} variant={isLandscape ? 'card' : 'sheet'}
@ -396,8 +400,9 @@ function PointList({
// ── Detail sheet ──────────────────────────────────────────────────────────── // ── Detail sheet ────────────────────────────────────────────────────────────
function PointDetail({ function PointDetail({
point, language, variant, width, top, onClose, apiKey, point, language, variant, width, top, onClose,
}: { }: {
apiKey: string
point: GeoPointDTO point: GeoPointDTO
language: string language: string
/// `sheet` : feuille basse (portrait). `card` : colonne à droite (paysage), /// `sheet` : feuille basse (portrait). `card` : colonne à droite (paysage),
@ -413,6 +418,10 @@ function PointDetail({
const prices = t(point.prices, language) const prices = t(point.prices, language)
const schedules = t(point.schedules, language) const schedules = t(point.schedules, language)
const isCard = variant === 'card' const isCard = variant === 'card'
const audio = useAudioResource(
point.audioIds?.find((a) => a.language === language)?.value ?? point.audioIds?.[0]?.value,
apiKey,
)
return ( return (
<> <>
@ -476,6 +485,15 @@ function PointDetail({
className="overflow-y-auto px-5 py-4 flex flex-col gap-4" className="overflow-y-auto px-5 py-4 flex flex-col gap-4"
style={isCard ? { flex: 1, minHeight: 0 } : { maxHeight: 'calc(80vh - 180px)' }} style={isCard ? { flex: 1, minHeight: 0 } : { maxHeight: 'calc(80vh - 180px)' }}
> >
{audio.url && (
<div className="flex flex-col gap-2">
{audio.narratorName && (
<NarratorAvatar name={audio.narratorName} portraitUrl={audio.narratorPortraitUrl} color="var(--color-text)" />
)}
<audio controls src={audio.url} style={{ width: '100%' }} />
</div>
)}
{t(point.description, language) && ( {t(point.description, language) && (
<div <div
className="text-sm leading-relaxed [&_p]:m-0 [&_p+p]:mt-2" className="text-sm leading-relaxed [&_p]:m-0 [&_p+p]:mt-2"

View File

@ -265,6 +265,8 @@ export interface GeoPointDTO {
site?: TranslationDTO[] site?: TranslationDTO[]
geometry?: GeometryDTO geometry?: GeometryDTO
polyColor?: string polyColor?: string
/** Un audio par langue : identifiants de ressource (lot 8e). */
audioIds?: TranslationDTO[]
} }
export interface CategorieDTO { export interface CategorieDTO {