Files

211 lines
5.8 KiB
TypeScript

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<void>;
updateCycle: (cycleId: number, patch: Partial<Cycle>) => void;
};
const FarmContext = createContext<FarmContextValue | null>(null);
export const FarmProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { user } = useAuth();
const [sites, setSites] = useState<Site[]>([]);
const [kandangs, setKandangs] = useState<Kandang[]>([]);
const [flocks, setFlocks] = useState<Flock[]>([]);
const [cycles, setCycles] = useState<Cycle[]>([]);
const [selectedSiteId, setSelectedSiteId] = useState<string | null>(null);
const [selectedKandangId, setSelectedKandangId] = useState<number | null>(null);
const [selectedCycleId, setSelectedCycleId] = useState<number | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [reloadToken, setReloadToken] = useState(0);
const reload = useCallback(async () => {
setReloadToken((token) => token + 1);
}, []);
const updateCycle = useCallback((cycleId: number, patch: Partial<Cycle>) => {
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 <FarmContext.Provider value={value}>{children}</FarmContext.Provider>;
};
export function useFarm(): FarmContextValue {
const ctx = useContext(FarmContext);
if (!ctx) throw new Error('useFarm must be used within FarmProvider');
return ctx;
}