1'use client';2import { useState } from 'react';3import { clientApi } from '@/lib/client-api';4import { fmtDate } from '@/lib/format';5import type { ChangeEvent } from '@/lib/types';6import { ChangeRow, groupByDay } from './change-row';78/**9 * Cursor pagination for /changes: the server renders the first page; this appends more via `before=<observed_at>`.10 * `qs` carries the active filters (category, type, importance_min, since…).11 */12export function LoadMore({ qs, initialCursor, lastDay }: { qs: string; initialCursor: string | null; lastDay: string | null }) {13 const [items, setItems] = useState<ChangeEvent[]>([]);14 const [cursor, setCursor] = useState<string | null>(initialCursor);15 const [loading, setLoading] = useState(false);16 const [error, setError] = useState(false);17 const groups = groupByDay(items);1819 const more = async () => {20 if (!cursor) return;21 setLoading(true);22 setError(false);23 try {24 const p = new URLSearchParams(qs);25 p.set('before', cursor);26 p.set('limit', '50');27 const res = await clientApi.changes(p.toString());28 setItems((prev) => [...prev, ...res.items]);29 const last = res.items[res.items.length - 1];30 setCursor(res.items.length < 50 || !last ? null : last.observed_at);31 } catch {32 setError(true);33 } finally {34 setLoading(false);35 }36 };3738 return (39 <>40 {groups.map((g, i) => (41 <section key={g.day} className="mt-8">42 {(i > 0 || g.day !== lastDay) && (43 <h2 className="eyebrow sticky top-[var(--header-h)] z-10 -mx-4 bg-canvas/95 px-4 py-2 backdrop-blur md:mx-0 md:px-0">44 {fmtDate(g.day)} <span className="tnum text-ink-3">{g.items.length}</span>45 </h2>46 )}47 <ul className="border-t border-rule">48 {g.items.map((e) => (49 <ChangeRow key={e.id} e={e} />50 ))}51 </ul>52 </section>53 ))}54 <div className="mt-6 flex items-center gap-3">55 {cursor ? (56 <button type="button" onClick={more} disabled={loading} className="h-10 border border-rule px-4 text-sm text-ink-2 hover:border-rule-strong hover:text-ink disabled:opacity-50">57 {loading ? 'Loading…' : 'Load older events'}58 </button>59 ) : (60 <p className="text-xs text-ink-3">End of the recorded history for these filters.</p>61 )}62 {error && <p className="text-xs text-danger">Could not load more — try again.</p>}63 </div>64 </>65 );66}67