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 { trackEvent } from '@/lib/stats'
import { getGeoPointLatLng } from '@/lib/geo'
import NarratorAvatar from '@/components/ui/NarratorAvatar'
import { useAudioResource } from '@/hooks/useAudioResource'
import './map/map.css'
const LeafletMap = dynamic(() => import('./map/LeafletMap'), {
@ -29,6 +31,7 @@ interface Props {
slug: string
configId: string
languages: string[]
apiKey: string
}
type Mode = 'map' | 'list'
@ -39,7 +42,7 @@ const DOCK_WIDTH = 340
const DETAIL_WIDTH = 320
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 back = useBack()
@ -322,6 +325,7 @@ export default function MapSection({ section, configId, languages }: Props) {
{/* Detail sheet (POI) */}
{selected && (
<PointDetail
apiKey={apiKey}
point={selected}
language={language}
variant={isLandscape ? 'card' : 'sheet'}
@ -396,8 +400,9 @@ function PointList({
// ── Detail sheet ────────────────────────────────────────────────────────────
function PointDetail({
point, language, variant, width, top, onClose,
apiKey, point, language, variant, width, top, onClose,
}: {
apiKey: string
point: GeoPointDTO
language: string
/// `sheet` : feuille basse (portrait). `card` : colonne à droite (paysage),
@ -413,6 +418,10 @@ function PointDetail({
const prices = t(point.prices, language)
const schedules = t(point.schedules, language)
const isCard = variant === 'card'
const audio = useAudioResource(
point.audioIds?.find((a) => a.language === language)?.value ?? point.audioIds?.[0]?.value,
apiKey,
)
return (
<>
@ -476,6 +485,15 @@ function PointDetail({
className="overflow-y-auto px-5 py-4 flex flex-col gap-4"
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) && (
<div
className="text-sm leading-relaxed [&_p]:m-0 [&_p+p]:mt-2"

View File

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