import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { api, errorMessage } from '../services/apiClient.ts'; import type { Cycle, Flock, Kandang, Site } from '../types/api.ts'; import { useAuth } from './AuthContext.tsx'; type FarmContextValue = { sites: Site[]; kandangs: Kandang[]; flocks: Flock[]; cycles: Cycle[]; selectedSiteId: string | null; selectedKandangId: number | null; selectedCycleId: number | null; selectedSite: Site | null; selectedKandang: Kandang | null; selectedCycle: Cycle | null; loading: boolean; error: string | null; setSelectedSiteId: (id: string | null) => void; setSelectedKandangId: (id: number | null) => void; setSelectedCycleId: (id: number | null) => void; reload: () => Promise; updateCycle: (cycleId: number, patch: Partial) => void; }; const FarmContext = createContext(null); export const FarmProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const { user } = useAuth(); const [sites, setSites] = useState([]); const [kandangs, setKandangs] = useState([]); const [flocks, setFlocks] = useState([]); const [cycles, setCycles] = useState([]); const [selectedSiteId, setSelectedSiteId] = useState(null); const [selectedKandangId, setSelectedKandangId] = useState(null); const [selectedCycleId, setSelectedCycleId] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [reloadToken, setReloadToken] = useState(0); const reload = useCallback(async () => { setReloadToken((token) => token + 1); }, []); const updateCycle = useCallback((cycleId: number, patch: Partial) => { setCycles((prev) => prev.map((c) => (c.id === cycleId ? { ...c, ...patch } : c))); }, []); useEffect(() => { if (!user) { setSites([]); setKandangs([]); setFlocks([]); setCycles([]); return; } let cancelled = false; (async () => { setLoading(true); setError(null); try { const nextSites = await api.sites.list(); if (cancelled) return; setSites(nextSites); setSelectedSiteId((prev) => { if (prev && nextSites.some((s) => s.id === prev)) return prev; return nextSites[0]?.id ?? null; }); } catch (err) { if (!cancelled) setError(errorMessage(err)); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, [user, reloadToken]); useEffect(() => { if (!user || selectedSiteId == null) { setKandangs([]); setSelectedKandangId(null); return; } let cancelled = false; (async () => { try { const next = await api.kandangs.list({ site_id: selectedSiteId }); if (cancelled) return; setKandangs(next); setSelectedKandangId((prev) => { if (prev && next.some((k) => k.id === prev)) return prev; return next[0]?.id ?? null; }); } catch (err) { if (!cancelled) setError(errorMessage(err)); } })(); return () => { cancelled = true; }; }, [user, selectedSiteId, reloadToken]); useEffect(() => { if (!user || selectedKandangId == null) { setFlocks([]); return; } let cancelled = false; (async () => { try { const next = await api.flocks.list({ kandang_id: selectedKandangId }); if (cancelled) return; setFlocks(next); } catch (err) { if (!cancelled) setError(errorMessage(err)); } })(); return () => { cancelled = true; }; }, [user, selectedKandangId, reloadToken]); useEffect(() => { if (!user || selectedKandangId == null) { setCycles([]); setSelectedCycleId(null); return; } let cancelled = false; (async () => { try { const next = await api.cycles.list({ kandang_id: selectedKandangId }); if (cancelled) return; setCycles(next); setSelectedCycleId((prev) => { if (prev && next.some((c) => c.id === prev)) return prev; const active = next.find((c) => c.status === 'active'); if (active) return active.id; return next[0]?.id ?? null; }); } catch (err) { if (!cancelled) setError(errorMessage(err)); } })(); return () => { cancelled = true; }; }, [user, selectedKandangId, reloadToken]); const selectedSite = useMemo( () => sites.find((s) => s.id === selectedSiteId) ?? null, [sites, selectedSiteId] ); const selectedKandang = useMemo( () => kandangs.find((k) => k.id === selectedKandangId) ?? null, [kandangs, selectedKandangId] ); const selectedCycle = useMemo( () => cycles.find((c) => c.id === selectedCycleId) ?? null, [cycles, selectedCycleId] ); const value = useMemo( () => ({ sites, kandangs, flocks, cycles, selectedSiteId, selectedKandangId, selectedCycleId, selectedSite, selectedKandang, selectedCycle, loading, error, setSelectedSiteId, setSelectedKandangId, setSelectedCycleId, reload, updateCycle, }), [ sites, kandangs, flocks, cycles, selectedSiteId, selectedKandangId, selectedCycleId, selectedSite, selectedKandang, selectedCycle, loading, error, reload, updateCycle, ] ); return {children}; }; export function useFarm(): FarmContextValue { const ctx = useContext(FarmContext); if (!ctx) throw new Error('useFarm must be used within FarmProvider'); return ctx; }