update all page to refresh immidiately without clicking sync button or refreshing the whole page
This commit is contained in:
1 parent
d5b0c12c40
commit
83a41fbbcb
15 files changed
+490
-151
No files matched your search
+72
-34
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { api, ApiError, errorMessage } from '../services/apiClient.ts';
|
||||
import type {
|
||||
ChickenCounting,
|
||||
@@ -44,7 +44,8 @@ function chronological<T>(rows: T[]): T[] {
|
||||
}
|
||||
|
||||
const Dashboard: React.FC<Props> = ({ setView }) => {
|
||||
const { selectedCycle, selectedKandang, selectedSite, selectedKandangId } = useFarm();
|
||||
const { selectedCycle, selectedKandang, selectedSite, selectedKandangId, reload, updateCycle } =
|
||||
useFarm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [kpis, setKpis] = useState<KPI[]>([]);
|
||||
@@ -56,27 +57,29 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
|
||||
const [reportOpen, setReportOpen] = useState(false);
|
||||
const [reportType, setReportType] = useState<DashboardReportType>('daily');
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedCycle) {
|
||||
setKpis([]);
|
||||
setCountings([]);
|
||||
setManualInputs([]);
|
||||
setWeights([]);
|
||||
setKarung(null);
|
||||
setIotDayAggregates([]);
|
||||
return;
|
||||
}
|
||||
const cycleId = selectedCycle.id;
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
setLoading(true);
|
||||
const fetchDashboardData = useCallback(
|
||||
async (isBackground = false) => {
|
||||
if (!selectedCycle) {
|
||||
setKpis([]);
|
||||
setCountings([]);
|
||||
setManualInputs([]);
|
||||
setWeights([]);
|
||||
setKarung(null);
|
||||
setIotDayAggregates([]);
|
||||
return;
|
||||
}
|
||||
const cycleId = selectedCycle.id;
|
||||
if (!isBackground) {
|
||||
setLoading(true);
|
||||
}
|
||||
setError(null);
|
||||
try {
|
||||
const [kpiRows, countingRows, manualRows, weightRows] = await Promise.all([
|
||||
const [kpiRows, countingRows, manualRows, weightRows, freshCycle] = await Promise.all([
|
||||
api.kpis.list({ cycle_id: cycleId }),
|
||||
api.countings.list({ cycle_id: cycleId }),
|
||||
api.manualInputs.list({ cycle_id: cycleId }),
|
||||
api.weights.list({ cycle_id: cycleId }),
|
||||
api.cycles?.retrieve?.(cycleId)?.catch(() => null) ?? Promise.resolve(null),
|
||||
]);
|
||||
let latestKarung: Karung | null = null;
|
||||
try {
|
||||
@@ -84,7 +87,9 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
|
||||
} catch (err) {
|
||||
if (!(err instanceof ApiError && err.status === 404)) throw err;
|
||||
}
|
||||
if (cancelled) return;
|
||||
if (freshCycle) {
|
||||
updateCycle(cycleId, freshCycle);
|
||||
}
|
||||
setKpis(kpiRows);
|
||||
setCountings(countingRows);
|
||||
setManualInputs(manualRows);
|
||||
@@ -115,29 +120,61 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
|
||||
);
|
||||
iotRows = batches.flat();
|
||||
}
|
||||
if (!cancelled) {
|
||||
setIotDayAggregates(
|
||||
buildIotDayAggregates({
|
||||
rows: iotRows,
|
||||
cycleStartDate: selectedCycle.start_date,
|
||||
getTempTarget: getTempStandardByDay,
|
||||
})
|
||||
);
|
||||
}
|
||||
setIotDayAggregates(
|
||||
buildIotDayAggregates({
|
||||
rows: iotRows,
|
||||
cycleStartDate: selectedCycle.start_date,
|
||||
getTempTarget: getTempStandardByDay,
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
if (!cancelled) setIotDayAggregates([]);
|
||||
setIotDayAggregates([]);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!cancelled) setError(errorMessage(err));
|
||||
if (!isBackground) {
|
||||
setError(errorMessage(err));
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
if (!isBackground) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectedCycle?.id, selectedCycle?.start_date, selectedKandang, updateCycle]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Initial fetch (with full loading indicator on cycle change)
|
||||
void fetchDashboardData(false);
|
||||
|
||||
// Auto-refresh interval: 1 minute (60,000 ms)
|
||||
const interval = setInterval(() => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
void fetchDashboardData(true);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
const handleFocusOrVisibility = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
void fetchDashboardData(true);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [selectedCycle, selectedKandang]);
|
||||
|
||||
document.addEventListener('visibilitychange', handleFocusOrVisibility);
|
||||
window.addEventListener('focus', handleFocusOrVisibility);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
document.removeEventListener('visibilitychange', handleFocusOrVisibility);
|
||||
window.removeEventListener('focus', handleFocusOrVisibility);
|
||||
};
|
||||
}, [fetchDashboardData]);
|
||||
|
||||
const handleManualInputSaved = useCallback(async () => {
|
||||
await fetchDashboardData(false);
|
||||
void reload();
|
||||
}, [fetchDashboardData, reload]);
|
||||
|
||||
const cards: KpiCardModel[] = useMemo(() => {
|
||||
// All cards share the latest complete KPI day (not calendar current_day / live IoT).
|
||||
@@ -475,6 +512,7 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
|
||||
cycleId={selectedCycle.id}
|
||||
currentDay={selectedCycle.current_day}
|
||||
readOnly={cycleStatus !== 'active'}
|
||||
onSaved={handleManualInputSaved}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user