update iot panel will now listen to push from panel
This commit is contained in:
1 parent
20d04acb3e
commit
520c4bcaad
10 files changed
+158
-754
No files matched your search
+17
-72
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user