fix hitung ayam dan karung data yang ditampilkan melebihi estimasi panen
CI / lint-and-test (push) Canceled after 0s
CI / lint-and-test (push) Canceled after 0s
This commit is contained in:
1 parent
32a36cceff
commit
b6117ae7d5
8 files changed
+104
-48
No files matched your search
@@ -95,8 +95,10 @@ class Cycle {
|
||||
const diffMs = today - startDate;
|
||||
const daysDiff = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
|
||||
// Use calculated day, but don't exceed totalDays
|
||||
calculatedCurrentDay = Math.min(daysDiff, cycle.total_days);
|
||||
// 0-based day index: day 0 = startDate, last day = startDate + (totalDays - 1)
|
||||
// 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 {
|
||||
|
||||
@@ -81,8 +81,10 @@ const ChickenCountingPage: React.FC = () => {
|
||||
const startDate = new Date(cycleData.startDate);
|
||||
today.setHours(0, 0, 0, 0);
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
const currentDayOfCycle = Math.floor(
|
||||
(today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)
|
||||
const lastCycleDay = Math.max(0, (cycleData.totalDays || 1) - 1);
|
||||
const currentDayOfCycle = Math.min(
|
||||
Math.floor((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)),
|
||||
lastCycleDay
|
||||
);
|
||||
|
||||
const recordMap = new Map<number, MortalitySourceRecord>();
|
||||
@@ -118,7 +120,7 @@ const ChickenCountingPage: React.FC = () => {
|
||||
|
||||
return { chickenCounts, populations };
|
||||
},
|
||||
[cycleData.startDate]
|
||||
[cycleData.startDate, cycleData.totalDays]
|
||||
);
|
||||
|
||||
// Fetch all data: total + per-kandang
|
||||
|
||||
@@ -60,6 +60,29 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
useState<FeedSackColumnConfig[]>(DEFAULT_FEED_SACK_COLUMNS);
|
||||
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 karungKpi = kpis.karungDituang;
|
||||
const reportedDay = Math.max(0, Math.round(kpis.umur?.value || 0));
|
||||
@@ -205,7 +228,7 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
columnConfig={columnConfig}
|
||||
startDate={new Date(cycleData.startDate)}
|
||||
saldoAwal={initialBalance?.saldoAwal || 0}
|
||||
effectiveDate={effectiveDate}
|
||||
effectiveDate={feedSaldoEffectiveDate}
|
||||
/>
|
||||
|
||||
{/* Chicken Counting Dashboard */}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
CycleFeedInitialBalance,
|
||||
} from '../types/counting.ts';
|
||||
import { normalizeFeedSackColumnConfigs } from '../utils/feedSackMapping.ts';
|
||||
import { getEffectiveCycleDay } from '../utils/dateFilter.ts';
|
||||
import { apiClient } from '../services/apiService.ts';
|
||||
import { View, CycleData } from '../types.ts';
|
||||
import { PageAiInsight } from './shared/PageAiInsight.tsx';
|
||||
@@ -86,28 +87,42 @@ const FeedSackCountingPage: React.FC<FeedSackCountingPageProps> = ({ setView })
|
||||
}, [cycles, selectedCycleId, cycleData]);
|
||||
|
||||
// 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 start = new Date(selectedCycle.startDate);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
const end = new Date(start);
|
||||
if (selectedCycle.status === 'Active') {
|
||||
// `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);
|
||||
}
|
||||
|
||||
end.setDate(end.getDate() + displayCurrentDay);
|
||||
return end;
|
||||
}, [selectedCycle.currentDay, selectedCycle.startDate, selectedCycle.status, effectiveDate]);
|
||||
}, [selectedCycle.startDate, displayCurrentDay]);
|
||||
|
||||
// Compute date range from selected cycle
|
||||
const { startDate, endDate } = useMemo(() => {
|
||||
@@ -163,16 +178,6 @@ const FeedSackCountingPage: React.FC<FeedSackCountingPageProps> = ({ setView })
|
||||
});
|
||||
}, [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
|
||||
const handleSaveInitialBalance = useCallback(
|
||||
async (saldoAwal: number) => {
|
||||
@@ -369,13 +374,25 @@ const FeedSackCountingPage: React.FC<FeedSackCountingPageProps> = ({ setView })
|
||||
hari_ke: displayCurrentDay,
|
||||
total_catatan_iot: filteredData.length,
|
||||
saldo_awal_karung: initialBalance?.saldoAwal ?? null,
|
||||
total_karung_masuk_iot: filteredData.reduce((sum: number, d: any) => sum + (d.countIn ?? 0), 0),
|
||||
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
|
||||
total_karung_masuk_iot: filteredData.reduce(
|
||||
(sum: number, d: any) => sum + (d.countIn ?? 0),
|
||||
0
|
||||
),
|
||||
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,
|
||||
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 */}
|
||||
<FeedSaldoCard
|
||||
iotData={feedSackHistory}
|
||||
iotData={filteredData}
|
||||
columnConfig={columnConfig}
|
||||
startDate={startDate}
|
||||
saldoAwal={initialBalance?.saldoAwal || 0}
|
||||
|
||||
@@ -178,8 +178,10 @@ const ChickenCountingDashboard: React.FC<ChickenCountingDashboardProps> = ({
|
||||
const startDate = new Date(cycleData.startDate);
|
||||
today.setHours(0, 0, 0, 0);
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
const currentDayOfCycle = Math.floor(
|
||||
(today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)
|
||||
const lastCycleDay = Math.max(0, (cycleData.totalDays || 1) - 1);
|
||||
const currentDayOfCycle = Math.min(
|
||||
Math.floor((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)),
|
||||
lastCycleDay
|
||||
);
|
||||
|
||||
const recordMap = new Map<number, MortalitySourceRecord>();
|
||||
@@ -213,7 +215,7 @@ const ChickenCountingDashboard: React.FC<ChickenCountingDashboardProps> = ({
|
||||
|
||||
return { chickenCounts, populations };
|
||||
},
|
||||
[cycleData.startDate]
|
||||
[cycleData.startDate, cycleData.totalDays]
|
||||
);
|
||||
|
||||
// Fetch all data: total + per-kandang
|
||||
|
||||
@@ -83,7 +83,11 @@ const CombinedTrendChart: React.FC<CombinedTrendChartProps> = ({
|
||||
today.setHours(0, 0, 0, 0);
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
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();
|
||||
sortedRecords.forEach((record: any) => {
|
||||
|
||||
@@ -200,7 +200,9 @@ const DataManagementPage: React.FC<DataManagementPageProps> = ({ selectedKandang
|
||||
const diffMs = todayMs - startDateMs;
|
||||
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
|
||||
const chickenCountMap: Record<number, number> = {};
|
||||
@@ -249,6 +251,7 @@ const DataManagementPage: React.FC<DataManagementPageProps> = ({ selectedKandang
|
||||
cycleData.id,
|
||||
cycleData.startDate,
|
||||
cycleData.currentDay,
|
||||
cycleData.totalDays,
|
||||
selectedKandangId,
|
||||
initialPopulation,
|
||||
calculateRecords,
|
||||
@@ -284,7 +287,9 @@ const DataManagementPage: React.FC<DataManagementPageProps> = ({ selectedKandang
|
||||
const diffMs = todayMs - startDateMs;
|
||||
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
|
||||
const chickenCountMap: Record<number, number> = {};
|
||||
|
||||
@@ -30,8 +30,9 @@ const FeedTrendChart: React.FC<FeedTrendChartProps> = ({
|
||||
const cycleStart = new Date(cycleStartDate);
|
||||
cycleStart.setHours(0, 0, 0, 0);
|
||||
|
||||
const now = new Date();
|
||||
now.setHours(0, 0, 0, 0);
|
||||
const cycleEnd = new Date(cycleStart);
|
||||
cycleEnd.setDate(cycleStart.getDate() + currentDay);
|
||||
cycleEnd.setHours(23, 59, 59, 999);
|
||||
|
||||
// Initialize daily buckets
|
||||
const dailyData: { [key: number]: Record<string, any> } = {};
|
||||
@@ -49,7 +50,7 @@ const FeedTrendChart: React.FC<FeedTrendChartProps> = ({
|
||||
|
||||
// Aggregate data by day
|
||||
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 dayNumber = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
|
||||
Reference in new issue
Block a user