import React, { useCallback, useEffect, useRef, useState } from "react"; import { View, Text, TouchableOpacity, ActivityIndicator, LayoutAnimation, useColorScheme, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import { Entry, entriesApi } from "../services/api"; import ExpandableItem from "./ExpandableItem"; // ─── Props ───────────────────────────────────────────────────────────────── // items removed — now fetched from API using groupName export interface CollapsibleCategoryProps { id: string; label: string; icon: keyof typeof Ionicons.glyphMap; color: string; darkColor: string; groupName: string; // passed to /entries?groupName= defaultOpen?: boolean; } // ─── Component ───────────────────────────────────────────────────────────── export default function CollapsibleCategory({ label, icon, color, darkColor, groupName, defaultOpen = false, }: CollapsibleCategoryProps) { const dark = useColorScheme() === "dark"; const accentColor = dark ? darkColor : color; const [open, setOpen] = useState(defaultOpen); const [items, setItems] = useState([]); const [page, setPage] = useState(0); const [hasMore, setHasMore] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const initialised = useRef(false); const fetchPage = useCallback(async (pageNum: number) => { if (loading) return; setLoading(true); setError(""); try { const data = await entriesApi.getEntries({ groupName, page: pageNum }); setItems((prev) => pageNum === 0 ? data.content : [...prev, ...data.content] ); setHasMore(!data.last); setPage(pageNum); } catch { setError("Couldn't load entries. Tap to retry."); } finally { setLoading(false); } }, [groupName, loading]); // Fetch first page the first time the accordion opens useEffect(() => { if (open && !initialised.current) { initialised.current = true; fetchPage(0); } }, [open]); const toggle = () => { LayoutAnimation.configureNext({ duration: 260, create: { type: "easeInEaseOut", property: "opacity" }, update: { type: "spring", springDamping: 0.85 }, }); setOpen((v) => !v); }; return ( {/* ── Header — untouched from your original ── */} {label} {/* Count badge — shows loaded count, spins while first load */} {loading && items.length === 0 ? ( ) : ( {items.length} )} {/* ── Body ── */} {open && ( {/* Error */} {error !== "" && ( fetchPage(page)} className="mx-4 py-4 items-center" > {error} )} {/* Items — same ExpandableItem you already have */} {items.map((item) => ( ))} {/* Load more */} {hasMore && items.length > 0 && ( fetchPage(page + 1)} disabled={loading} className={`mx-4 mt-1 mb-1 py-3 rounded-2xl border items-center ${ dark ? "bg-obsidian-50 border-border-dark" : "bg-parchment-100 border-border-light" }`} activeOpacity={0.7} > {loading ? ( ) : ( Load more )} )} {/* End of list */} {!hasMore && items.length > 0 && ( All {items.length} entries loaded )} )} ); }