SPB Git

spb/ai-atlas

Public
2.5 KB · 67 lines tsx
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
Files · 9f1db171c786cf4a3b99436329f29b145c02aeec
Menu

Theme