2025-12-23 02:20:57 +01:00

138 lines
3.8 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react'
import type { Series } from '@/types/nostr'
import { SeriesList } from './SeriesList'
import { SeriesStats } from './SeriesStats'
import { getSeriesByAuthor } from '@/lib/seriesQueries'
import { getSeriesAggregates } from '@/lib/seriesAggregation'
interface SeriesSectionProps {
authorPubkey: string
onSelect: (seriesId: string | undefined) => void
selectedId?: string | undefined
}
export function SeriesSection({ authorPubkey, onSelect, selectedId }: SeriesSectionProps) {
const [{ series, loading, error, aggregates }, load] = useSeriesData(authorPubkey)
if (loading) {
return <p className="text-sm text-gray-600">Chargement des séries...</p>
}
if (error) {
return <p className="text-sm text-red-600">{error}</p>
}
return (
<div className="space-y-4">
<SeriesControls onSelect={onSelect} onReload={load} />
<SeriesList
series={series}
onSelect={onSelect}
{...(selectedId ? { selectedId } : {})}
/>
<SeriesAggregatesList series={series} aggregates={aggregates} />
</div>
)
}
function SeriesControls({
onSelect,
onReload,
}: {
onSelect: (id: string | undefined) => void
onReload: () => Promise<void>
}) {
return (
<div className="flex items-center gap-2">
<button
type="button"
className="px-3 py-1 text-sm rounded bg-gray-200"
onClick={() => onSelect(undefined)}
>
Toutes les séries
</button>
<button
type="button"
className="text-xs text-blue-600 underline"
onClick={() => {
void onReload()
}}
>
Recharger
</button>
</div>
)
}
function SeriesAggregatesList({
series,
aggregates,
}: {
series: Series[]
aggregates: Record<string, { sponsoring: number; purchases: number; reviewTips: number }>
}) {
return (
<>
{series.map((s) => {
const agg = aggregates[s.id] ?? { sponsoring: 0, purchases: 0, reviewTips: 0 }
return (
<div key={s.id} className="mt-2">
<SeriesStats sponsoring={agg.sponsoring} purchases={agg.purchases} reviewTips={agg.reviewTips} />
</div>
)
})}
</>
)
}
function useSeriesData(authorPubkey: string): [
{
series: Series[]
loading: boolean
error: string | null
aggregates: Record<string, { sponsoring: number; purchases: number; reviewTips: number }>
},
() => Promise<void>
] {
const [series, setSeries] = useState<Series[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [aggregates, setAggregates] = useState<Record<string, { sponsoring: number; purchases: number; reviewTips: number }>>({})
const load = useCallback(async () => {
setLoading(true)
setError(null)
try {
const { items, aggregates: agg } = await fetchSeriesAndAggregates(authorPubkey)
setSeries(items)
setAggregates(agg)
} catch (e) {
setError(e instanceof Error ? e.message : 'Erreur lors du chargement des séries')
} finally {
setLoading(false)
}
}, [authorPubkey])
useEffect(() => {
void load()
}, [load])
return [{ series, loading, error, aggregates }, load]
}
async function fetchSeriesAndAggregates(authorPubkey: string): Promise<{
items: Series[]
aggregates: Record<string, { sponsoring: number; purchases: number; reviewTips: number }>
}> {
const items = await getSeriesByAuthor(authorPubkey)
const aggEntries = await Promise.all(
items.map(async (s) => {
const agg = await getSeriesAggregates({ authorPubkey, seriesId: s.id })
return [s.id, agg] as const
})
)
const aggMap: Record<string, { sponsoring: number; purchases: number; reviewTips: number }> = {}
aggEntries.forEach(([id, agg]) => {
aggMap[id] = agg
})
return { items, aggregates: aggMap }
}