update iot panel will now listen to push from panel

This commit is contained in:
Alberto-Audrix committed 2026-09-21 14:38:17 +07:00
1 parent 20d04acb3e
commit 520c4bcaad
10 files changed
+158 -754

No files matched your search

+17 -72
View File
@@ -65,16 +65,11 @@ const IotPanelPage: React.FC<{ onSettings: () => void }> = ({ onSettings }) => {
const [selectedDate, setSelectedDate] = useState('');
const [dayRows, setDayRows] = useState<PanelIoT[]>([]);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [refreshToken, setRefreshToken] = useState(0);
const [selectedChartKey, setSelectedChartKey] = useState<'humidity' | 'wind' | 'water' | null>(
null
);
const selectedFlock = flocks.find((flock) => flock.id === selectedFlockId) ?? null;
const hasIotUrl = Boolean((selectedFlock?.iot_api_url || '').trim());
useEffect(() => {
if (!selectedChartKey) return;
const onKey = (event: KeyboardEvent) => {
@@ -140,7 +135,6 @@ const IotPanelPage: React.FC<{ onSettings: () => void }> = ({ onSettings }) => {
? previous
: (sortedDates[sortedDates.length - 1] ?? fallbackDate);
}
// No IoT dates yet — keep a navigable date inside the cycle window.
if (previous && previous >= cycleMin && previous <= cycleMax) return previous;
return fallbackDate;
});
@@ -153,7 +147,7 @@ const IotPanelPage: React.FC<{ onSettings: () => void }> = ({ onSettings }) => {
return () => {
cancelled = true;
};
}, [selectedCycle, selectedFlockId, refreshToken]);
}, [selectedCycle, selectedFlockId]);
useEffect(() => {
if (!selectedCycle || selectedFlockId == null || !selectedDate) {
@@ -185,7 +179,7 @@ const IotPanelPage: React.FC<{ onSettings: () => void }> = ({ onSettings }) => {
return () => {
cancelled = true;
};
}, [selectedCycle, selectedFlockId, selectedDate, refreshToken]);
}, [selectedCycle, selectedFlockId, selectedDate]);
const summary = useMemo<DaySummary | null>(() => {
if (dayRows.length === 0) return null;
@@ -258,40 +252,9 @@ const IotPanelPage: React.FC<{ onSettings: () => void }> = ({ onSettings }) => {
});
}, [selectedKandang, selectedCycle, summary, dayAge, selectedDate]);
// Always allow navigating the cycle window — never hide empty days behind missing IoT dates.
const datePickerMin = selectedCycle?.start_date ?? undefined;
const datePickerMax = selectedCycle ? resolveCycleBoundDate(selectedCycle) : undefined;
const pullFromApi = async () => {
if (!selectedFlockId || !hasIotUrl) return;
setRefreshing(true);
setError(null);
try {
await api.panelIot.sync({ flock_id: selectedFlockId });
setRefreshToken((token) => token + 1);
} catch (err) {
setError(errorMessage(err));
} finally {
setRefreshing(false);
}
};
useEffect(() => {
if (!selectedFlockId || !hasIotUrl) return;
const id = window.setInterval(() => {
if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
void (async () => {
try {
await api.panelIot.sync({ flock_id: selectedFlockId });
setRefreshToken((token) => token + 1);
} catch {
// Live follow stays quiet; manual pull surfaces errors.
}
})();
}, 60_000);
return () => window.clearInterval(id);
}, [selectedFlockId, hasIotUrl]);
if (!selectedCycle) return <EmptyFarm onSettings={onSettings} />;
return (
@@ -301,44 +264,26 @@ const IotPanelPage: React.FC<{ onSettings: () => void }> = ({ onSettings }) => {
subtitle={`${selectedSite?.site_name ?? ''} · ${selectedKandang?.kandang_name ?? ''} · Siklus ${formatDateRange(selectedCycle.start_date, selectedCycle.end_date)}`}
icon={<i className="fa-solid fa-table-columns" />}
actions={
<div className="flex items-center gap-2">
<select
value={selectedFlockId ?? ''}
onChange={(event) =>
setSelectedFlockId(event.target.value ? Number(event.target.value) : null)
}
className="rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-700"
aria-label="Pilih flock"
>
{flocks.length === 0 && <option value="">Tidak ada flock</option>}
{flocks.map((flock) => (
<option key={flock.id} value={flock.id}>
{flock.flock_name}
</option>
))}
</select>
<button
type="button"
onClick={() => void pullFromApi()}
disabled={refreshing || !hasIotUrl}
className="rounded-lg border border-slate-300 bg-slate-50 px-3 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-100 disabled:opacity-60"
title={hasIotUrl ? 'Tarik data dari API IoT' : 'Setel URL IoT di Pengaturan'}
>
<i className={`fa-solid fa-rotate-right mr-1 ${refreshing ? 'fa-spin' : ''}`} />
{refreshing ? 'Sinkron…' : 'Tarik API'}
</button>
</div>
<select
value={selectedFlockId ?? ''}
onChange={(event) =>
setSelectedFlockId(event.target.value ? Number(event.target.value) : null)
}
className="rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-700"
aria-label="Pilih flock"
>
{flocks.length === 0 && <option value="">Tidak ada flock</option>}
{flocks.map((flock) => (
<option key={flock.id} value={flock.id}>
{flock.flock_name}
</option>
))}
</select>
}
/>
{error && <Banner kind="error">{error}</Banner>}
{selectedFlockId != null && !hasIotUrl && (
<Banner kind="error">
Flock ini belum punya URL API Panel IoT. Setel di Pengaturan → Flock.
</Banner>
)}
{selectedFlockId == null ? (
<div className={cardClass}>
<p className="py-8 text-center text-slate-400">Pilih flock untuk melihat data panel IoT.</p>