fix hitung ayam dan karung data yang ditampilkan melebihi estimasi panen
CI / lint-and-test (push) Canceled after 0s

This commit is contained in:
Alberto-Audrix committed 2026-07-29 10:48:03 +07:00
1 parent 32a36cceff
commit b6117ae7d5
8 files changed
+105 -49

No files matched your search

+4 -2
View File
@@ -95,8 +95,10 @@ class Cycle {
const diffMs = today - startDate; const diffMs = today - startDate;
const daysDiff = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const daysDiff = Math.floor(diffMs / (1000 * 60 * 60 * 24));
// Use calculated day, but don't exceed totalDays // 0-based day index: day 0 = startDate, last day = startDate + (totalDays - 1)
calculatedCurrentDay = Math.min(daysDiff, cycle.total_days); // Cap at totalDays - 1 so currentDay never maps past Estimasi Panen / end_date
const lastDayIndex = Math.max(0, cycle.total_days - 1);
calculatedCurrentDay = Math.min(daysDiff, lastDayIndex);
} }
return { return {
+5 -3
View File
@@ -81,8 +81,10 @@ const ChickenCountingPage: React.FC = () => {
const startDate = new Date(cycleData.startDate); const startDate = new Date(cycleData.startDate);
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
startDate.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0);
const currentDayOfCycle = Math.floor( const lastCycleDay = Math.max(0, (cycleData.totalDays || 1) - 1);
(today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24) const currentDayOfCycle = Math.min(
Math.floor((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)),
lastCycleDay
); );
const recordMap = new Map<number, MortalitySourceRecord>(); const recordMap = new Map<number, MortalitySourceRecord>();
@@ -118,7 +120,7 @@ const ChickenCountingPage: React.FC = () => {
return { chickenCounts, populations }; return { chickenCounts, populations };
}, },
[cycleData.startDate] [cycleData.startDate, cycleData.totalDays]
); );
// Fetch all data: total + per-kandang // Fetch all data: total + per-kandang
+24 -1
View File
@@ -60,6 +60,29 @@ const Dashboard: React.FC<DashboardProps> = ({
useState<FeedSackColumnConfig[]>(DEFAULT_FEED_SACK_COLUMNS); useState<FeedSackColumnConfig[]>(DEFAULT_FEED_SACK_COLUMNS);
const [initialBalance, setInitialBalance] = useState<CycleFeedInitialBalance | null>(null); const [initialBalance, setInitialBalance] = useState<CycleFeedInitialBalance | null>(null);
const feedSaldoEffectiveDate = useMemo(() => {
const cappedEffectiveDate = new Date(effectiveDate);
cappedEffectiveDate.setHours(0, 0, 0, 0);
if (!cycleData.startDate) {
return cappedEffectiveDate;
}
const cycleEndDate = cycleData.endDate
? new Date(cycleData.endDate)
: (() => {
const derivedEndDate = new Date(cycleData.startDate);
derivedEndDate.setDate(
derivedEndDate.getDate() + Math.max(0, (cycleData.totalDays || 1) - 1)
);
return derivedEndDate;
})();
cycleEndDate.setHours(0, 0, 0, 0);
return cappedEffectiveDate <= cycleEndDate ? cappedEffectiveDate : cycleEndDate;
}, [cycleData.endDate, cycleData.startDate, cycleData.totalDays, effectiveDate]);
const displayedKpis = useMemo(() => { const displayedKpis = useMemo(() => {
const karungKpi = kpis.karungDituang; const karungKpi = kpis.karungDituang;
const reportedDay = Math.max(0, Math.round(kpis.umur?.value || 0)); const reportedDay = Math.max(0, Math.round(kpis.umur?.value || 0));
@@ -205,7 +228,7 @@ const Dashboard: React.FC<DashboardProps> = ({
columnConfig={columnConfig} columnConfig={columnConfig}
startDate={new Date(cycleData.startDate)} startDate={new Date(cycleData.startDate)}
saldoAwal={initialBalance?.saldoAwal || 0} saldoAwal={initialBalance?.saldoAwal || 0}
effectiveDate={effectiveDate} effectiveDate={feedSaldoEffectiveDate}
/> />
{/* Chicken Counting Dashboard */} {/* Chicken Counting Dashboard */}
+51 -34
View File
@@ -14,6 +14,7 @@ import {
CycleFeedInitialBalance, CycleFeedInitialBalance,
} from '../types/counting.ts'; } from '../types/counting.ts';
import { normalizeFeedSackColumnConfigs } from '../utils/feedSackMapping.ts'; import { normalizeFeedSackColumnConfigs } from '../utils/feedSackMapping.ts';
import { getEffectiveCycleDay } from '../utils/dateFilter.ts';
import { apiClient } from '../services/apiService.ts'; import { apiClient } from '../services/apiService.ts';
import { View, CycleData } from '../types.ts'; import { View, CycleData } from '../types.ts';
import { PageAiInsight } from './shared/PageAiInsight.tsx'; import { PageAiInsight } from './shared/PageAiInsight.tsx';
@@ -86,28 +87,42 @@ const FeedSackCountingPage: React.FC<FeedSackCountingPageProps> = ({ setView })
}, [cycles, selectedCycleId, cycleData]); }, [cycles, selectedCycleId, cycleData]);
// Compute the display end date for the selected cycle. // Compute the display end date for the selected cycle.
// Active cycle follows the homepage D-1 rule; completed cycles keep their stored day. // Active cycle follows the homepage D-1 rule; completed cycles use endDate/currentDay.
// Always cap at totalDays - 1 so data never extends past Estimasi Panen.
const displayCurrentDay = useMemo(() => {
const lastCycleDay = Math.max(0, (selectedCycle.totalDays || 1) - 1);
if (selectedCycle.status === 'Active') {
const rawDay = Math.max(0, getEffectiveCycleDay(selectedCycle.startDate));
return Math.min(rawDay, lastCycleDay);
}
if (selectedCycle.endDate) {
const start = new Date(selectedCycle.startDate);
start.setHours(0, 0, 0, 0);
const cycleEnd = new Date(selectedCycle.endDate);
cycleEnd.setHours(0, 0, 0, 0);
const dayFromEnd = Math.floor((cycleEnd.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
return Math.min(Math.max(0, dayFromEnd), lastCycleDay);
}
return Math.min(Math.max(0, selectedCycle.currentDay ?? 0), lastCycleDay);
}, [
selectedCycle.currentDay,
selectedCycle.endDate,
selectedCycle.startDate,
selectedCycle.status,
selectedCycle.totalDays,
effectiveDate,
]);
const displayDate = useMemo(() => { const displayDate = useMemo(() => {
const start = new Date(selectedCycle.startDate); const start = new Date(selectedCycle.startDate);
start.setHours(0, 0, 0, 0); start.setHours(0, 0, 0, 0);
const end = new Date(start); const end = new Date(start);
if (selectedCycle.status === 'Active') { end.setDate(end.getDate() + displayCurrentDay);
// `effectiveDate` from AppContext is already D-1 adjusted, so we only
// compute the cycle-day offset here without applying the cutoff logic again.
const effectiveDateOnly = new Date(effectiveDate);
effectiveDateOnly.setHours(0, 0, 0, 0);
const effectiveCurrentDay = Math.max(
0,
Math.floor((effectiveDateOnly.getTime() - start.getTime()) / (1000 * 60 * 60 * 24))
);
end.setDate(end.getDate() + effectiveCurrentDay);
} else {
end.setDate(end.getDate() + selectedCycle.currentDay);
}
return end; return end;
}, [selectedCycle.currentDay, selectedCycle.startDate, selectedCycle.status, effectiveDate]); }, [selectedCycle.startDate, displayCurrentDay]);
// Compute date range from selected cycle // Compute date range from selected cycle
const { startDate, endDate } = useMemo(() => { const { startDate, endDate } = useMemo(() => {
@@ -163,16 +178,6 @@ const FeedSackCountingPage: React.FC<FeedSackCountingPageProps> = ({ setView })
}); });
}, [feedSackHistory, startDate, endDate]); }, [feedSackHistory, startDate, endDate]);
// Calculate currentDay from selected cycle
const displayCurrentDay = useMemo(() => {
const start = new Date(selectedCycle.startDate);
start.setHours(0, 0, 0, 0);
const end = new Date(displayDate);
end.setHours(0, 0, 0, 0);
const diffDays = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
return Math.max(0, diffDays);
}, [selectedCycle.startDate, displayDate]);
// Handler for saving initial balance // Handler for saving initial balance
const handleSaveInitialBalance = useCallback( const handleSaveInitialBalance = useCallback(
async (saldoAwal: number) => { async (saldoAwal: number) => {
@@ -369,13 +374,25 @@ const FeedSackCountingPage: React.FC<FeedSackCountingPageProps> = ({ setView })
hari_ke: displayCurrentDay, hari_ke: displayCurrentDay,
total_catatan_iot: filteredData.length, total_catatan_iot: filteredData.length,
saldo_awal_karung: initialBalance?.saldoAwal ?? null, saldo_awal_karung: initialBalance?.saldoAwal ?? null,
total_karung_masuk_iot: filteredData.reduce((sum: number, d: any) => sum + (d.countIn ?? 0), 0), total_karung_masuk_iot: filteredData.reduce(
total_karung_dituang_iot: filteredData.reduce((sum: number, d: any) => sum + (d.countOut ?? 0), 0), (sum: number, d: any) => sum + (d.countIn ?? 0),
rata_karung_per_hari: displayCurrentDay > 0 0
? Math.round(filteredData.reduce((sum: number, d: any) => sum + (d.countOut ?? 0), 0) / displayCurrentDay * 10) / 10 ),
: null, total_karung_dituang_iot: filteredData.reduce(
(sum: number, d: any) => sum + (d.countOut ?? 0),
0
),
rata_karung_per_hari:
displayCurrentDay > 0
? Math.round(
(filteredData.reduce((sum: number, d: any) => sum + (d.countOut ?? 0), 0) /
displayCurrentDay) *
10
) / 10
: null,
total_entry_manual: manualFeedSackEntries.length, total_entry_manual: manualFeedSackEntries.length,
catatan: 'Karung masuk = stok diterima, karung dituang = pakan dikonsumsi oleh ayam.', catatan:
'Karung masuk = stok diterima, karung dituang = pakan dikonsumsi oleh ayam.',
}} }}
/> />
)} )}
@@ -396,7 +413,7 @@ const FeedSackCountingPage: React.FC<FeedSackCountingPageProps> = ({ setView })
{/* Saldo Karung Card */} {/* Saldo Karung Card */}
<FeedSaldoCard <FeedSaldoCard
iotData={feedSackHistory} iotData={filteredData}
columnConfig={columnConfig} columnConfig={columnConfig}
startDate={startDate} startDate={startDate}
saldoAwal={initialBalance?.saldoAwal || 0} saldoAwal={initialBalance?.saldoAwal || 0}
@@ -178,8 +178,10 @@ const ChickenCountingDashboard: React.FC<ChickenCountingDashboardProps> = ({
const startDate = new Date(cycleData.startDate); const startDate = new Date(cycleData.startDate);
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
startDate.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0);
const currentDayOfCycle = Math.floor( const lastCycleDay = Math.max(0, (cycleData.totalDays || 1) - 1);
(today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24) const currentDayOfCycle = Math.min(
Math.floor((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)),
lastCycleDay
); );
const recordMap = new Map<number, MortalitySourceRecord>(); const recordMap = new Map<number, MortalitySourceRecord>();
@@ -213,7 +215,7 @@ const ChickenCountingDashboard: React.FC<ChickenCountingDashboardProps> = ({
return { chickenCounts, populations }; return { chickenCounts, populations };
}, },
[cycleData.startDate] [cycleData.startDate, cycleData.totalDays]
); );
// Fetch all data: total + per-kandang // Fetch all data: total + per-kandang
+5 -1
View File
@@ -83,7 +83,11 @@ const CombinedTrendChart: React.FC<CombinedTrendChartProps> = ({
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
startDate.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0);
const startDateMs = startDate.getTime(); const startDateMs = startDate.getTime();
const currentDayOfCycle = Math.floor((today.getTime() - startDateMs) / (1000 * 60 * 60 * 24)); const lastCycleDay = Math.max(0, (cycleData.totalDays || 1) - 1);
const currentDayOfCycle = Math.min(
Math.floor((today.getTime() - startDateMs) / (1000 * 60 * 60 * 24)),
lastCycleDay
);
const recordMap = new Map(); const recordMap = new Map();
sortedRecords.forEach((record: any) => { sortedRecords.forEach((record: any) => {
+7 -2
View File
@@ -200,7 +200,9 @@ const DataManagementPage: React.FC<DataManagementPageProps> = ({ selectedKandang
const diffMs = todayMs - startDateMs; const diffMs = todayMs - startDateMs;
const actualCurrentDay = diffMs >= 0 ? Math.floor(diffMs / (1000 * 60 * 60 * 24)) : 0; const actualCurrentDay = diffMs >= 0 ? Math.floor(diffMs / (1000 * 60 * 60 * 24)) : 0;
const maxDay = cycleData.currentDay; // Last inclusive 0-based day index matches Estimasi Panen (startDate + totalDays - 1)
const lastCycleDay = Math.max(0, (cycleData.totalDays || 1) - 1);
const maxDay = Math.min(cycleData.currentDay ?? 0, lastCycleDay);
// Fetch chicken counting data // Fetch chicken counting data
const chickenCountMap: Record<number, number> = {}; const chickenCountMap: Record<number, number> = {};
@@ -249,6 +251,7 @@ const DataManagementPage: React.FC<DataManagementPageProps> = ({ selectedKandang
cycleData.id, cycleData.id,
cycleData.startDate, cycleData.startDate,
cycleData.currentDay, cycleData.currentDay,
cycleData.totalDays,
selectedKandangId, selectedKandangId,
initialPopulation, initialPopulation,
calculateRecords, calculateRecords,
@@ -284,7 +287,9 @@ const DataManagementPage: React.FC<DataManagementPageProps> = ({ selectedKandang
const diffMs = todayMs - startDateMs; const diffMs = todayMs - startDateMs;
const actualCurrentDay = diffMs >= 0 ? Math.floor(diffMs / (1000 * 60 * 60 * 24)) : 0; const actualCurrentDay = diffMs >= 0 ? Math.floor(diffMs / (1000 * 60 * 60 * 24)) : 0;
const maxDay = cycleData.currentDay; // Last inclusive 0-based day index matches Estimasi Panen (startDate + totalDays - 1)
const lastCycleDay = Math.max(0, (cycleData.totalDays || 1) - 1);
const maxDay = Math.min(cycleData.currentDay ?? 0, lastCycleDay);
// Fetch chicken counting data // Fetch chicken counting data
const chickenCountMap: Record<number, number> = {}; const chickenCountMap: Record<number, number> = {};
+4 -3
View File
@@ -30,8 +30,9 @@ const FeedTrendChart: React.FC<FeedTrendChartProps> = ({
const cycleStart = new Date(cycleStartDate); const cycleStart = new Date(cycleStartDate);
cycleStart.setHours(0, 0, 0, 0); cycleStart.setHours(0, 0, 0, 0);
const now = new Date(); const cycleEnd = new Date(cycleStart);
now.setHours(0, 0, 0, 0); cycleEnd.setDate(cycleStart.getDate() + currentDay);
cycleEnd.setHours(23, 59, 59, 999);
// Initialize daily buckets // Initialize daily buckets
const dailyData: { [key: number]: Record<string, any> } = {}; const dailyData: { [key: number]: Record<string, any> } = {};
@@ -49,7 +50,7 @@ const FeedTrendChart: React.FC<FeedTrendChartProps> = ({
// Aggregate data by day // Aggregate data by day
data.forEach((log) => { data.forEach((log) => {
if (log.timestamp >= cycleStart && log.timestamp <= now) { if (log.timestamp >= cycleStart && log.timestamp <= cycleEnd) {
const diffTime = log.timestamp.getTime() - cycleStart.getTime(); const diffTime = log.timestamp.getTime() - cycleStart.getTime();
const dayNumber = Math.floor(diffTime / (1000 * 60 * 60 * 24)); const dayNumber = Math.floor(diffTime / (1000 * 60 * 60 * 24));