211 lines
5.8 KiB
TypeScript
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;
|
|
}
|