update sync data from site dashboard and testing closing cycles

This commit is contained in:
Alberto-Audrix committed 2026-09-15 16:52:41 +07:00
1 parent 36639d8c38
commit f49e72592c
42 files changed
+1966 -688

No files matched your search

+33 -31
View File
@@ -6,7 +6,6 @@ import type {
ChickenWeight,
Karung,
KPI,
LatestAvg,
ManualInput,
} from '../types/api.ts';
import type { View } from '../types/common.ts';
@@ -20,7 +19,7 @@ import {
formatNumber,
resolveCycleStatus,
} from '../utils/format.ts';
import { isOnOrBeforeVisibleDate, throughVisibleDate } from '../utils/visibility.ts';
import { throughVisibleDate } from '../utils/visibility.ts';
import { getCountingAccuracyWarning } from '../utils/countingAccuracyWarning.ts';
import DashboardReportCtas from './dashboard/DashboardReportCtas.tsx';
import ReportPreviewOverlay from './dashboard/ReportPreviewOverlay.tsx';
@@ -44,7 +43,6 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
const [manualInputs, setManualInputs] = useState<ManualInput[]>([]);
const [weights, setWeights] = useState<ChickenWeight[]>([]);
const [karung, setKarung] = useState<Karung | null>(null);
const [latestAvg, setLatestAvg] = useState<LatestAvg | null>(null);
const [insights, setInsights] = useState<AIInsight[]>([]);
const [reportOpen, setReportOpen] = useState(false);
const [reportType, setReportType] = useState<DashboardReportType>('daily');
@@ -56,7 +54,6 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
setManualInputs([]);
setWeights([]);
setKarung(null);
setLatestAvg(null);
setInsights([]);
return;
}
@@ -79,19 +76,12 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
} catch (err) {
if (!(err instanceof ApiError && err.status === 404)) throw err;
}
let avg: LatestAvg | null = null;
try {
avg = await api.weights.latestAvg({ cycle_id: cycleId });
} catch (err) {
if (!(err instanceof ApiError && err.status === 404)) throw err;
}
if (cancelled) return;
setKpis(kpiRows);
setCountings(countingRows);
setManualInputs(manualRows);
setWeights(weightRows);
setKarung(latestKarung);
setLatestAvg(avg);
setInsights(insightRows);
} catch (err) {
if (!cancelled) setError(errorMessage(err));
@@ -105,40 +95,52 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
}, [selectedCycle]);
const cards: KpiCardModel[] = useMemo(() => {
// Countings/weights APIs are real-time; KPI cards still honor publish cutoff.
// All cards share the latest complete KPI day (not calendar current_day / live IoT).
// Synced KPI rows only exist for days that had manual + sources on the site.
const visibleThrough = selectedCycle?.visible_through_date;
const visibleKpis = throughVisibleDate(kpis, visibleThrough);
const visibleCountings = throughVisibleDate(countings, visibleThrough);
const visibleWeights = throughVisibleDate(weights, visibleThrough);
const visibleLatestAvg =
latestAvg && isOnOrBeforeVisibleDate(latestAvg.date, visibleThrough) ? latestAvg : null;
const visibleKarung =
karung && isOnOrBeforeVisibleDate(karung.date, visibleThrough) ? karung : null;
const latest = visibleKpis[0] ?? null;
const anchorDate = latest?.date ?? null;
const kpisThroughAnchor = anchorDate
? visibleKpis.filter((row) => row.date <= anchorDate)
: [];
const weightsThroughAnchor = anchorDate
? throughVisibleDate(weights, visibleThrough).filter((row) => row.date <= anchorDate)
: [];
const weightOnAnchor = anchorDate
? weightsThroughAnchor.find((row) => row.date === anchorDate) ?? weightsThroughAnchor[0]
: null;
const latest = visibleKpis[0];
const startDate = selectedCycle?.start_date;
const trendLabel = (isoDate: string): string => {
const day = startDate ? cycleDayForDate(startDate, isoDate) : 0;
return `Hari ${day} · ${formatDateId(isoDate)}`;
};
const fcrTrend: KpiTrendPoint[] = chronological(visibleKpis).map((row) => ({
const fcrTrend: KpiTrendPoint[] = chronological(kpisThroughAnchor).map((row) => ({
value: row.fcr,
label: trendLabel(row.date),
}));
const eefTrend: KpiTrendPoint[] = chronological(visibleKpis).map((row) => ({
const eefTrend: KpiTrendPoint[] = chronological(kpisThroughAnchor).map((row) => ({
value: row.eef,
label: trendLabel(row.date),
}));
const mortalityTrend: KpiTrendPoint[] = chronological(visibleCountings).map((row) => ({
value: row.mortality_count,
const mortalityTrend: KpiTrendPoint[] = chronological(kpisThroughAnchor).map((row) => ({
value: row.mortality,
label: trendLabel(row.date),
}));
const mortalityToday = latest?.mortality ?? visibleCountings[0]?.mortality_count ?? null;
const weightTrend: KpiTrendPoint[] = chronological(visibleWeights).map((row) => ({
const weightTrend: KpiTrendPoint[] = chronological(weightsThroughAnchor).map((row) => ({
value: row.average_weight,
label: trendLabel(row.date),
}));
const age = selectedCycle?.current_day ?? latest?.age ?? null;
const age = latest?.age ?? null;
const mortalityToday = latest?.mortality ?? null;
const feedToday = latest != null ? latest.feed : null;
const weightToday =
latest && latest.iot_weight > 0
? latest.iot_weight
: (weightOnAnchor?.average_weight ?? null);
return [
{
key: 'umur',
@@ -161,20 +163,20 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
{
key: 'karung',
title: 'Karung Dituang',
value: visibleKarung?.feed_use_today ?? visibleKarung?.out_today ?? null,
value: feedToday,
unit: 'karung',
icon: '🛢️',
status: visibleKarung ? 'ok' : 'na',
status: feedToday == null ? 'na' : 'ok',
targetView: 'feedSackCounting',
},
{
key: 'weight',
title: 'Berat AVG',
value: visibleLatestAvg?.average_weight ?? visibleWeights[0]?.average_weight ?? null,
value: weightToday,
unit: 'gram',
icon: '⚖️',
trend: weightTrend,
status: visibleLatestAvg || visibleWeights[0] ? 'ok' : 'na',
status: weightToday == null ? 'na' : 'ok',
targetView: 'weightMonitoring',
},
{
@@ -198,7 +200,7 @@ const Dashboard: React.FC<Props> = ({ setView }) => {
targetView: 'eefDashboard',
},
];
}, [kpis, countings, weights, karung, latestAvg, selectedCycle]);
}, [kpis, weights, selectedCycle]);
const countingAccuracyWarning = useMemo(() => {
if (!selectedCycle) return null;