import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { api, errorMessage } from '../services/apiClient.ts'; import type { Cycle, Karung, ManualInput } from '../types/api.ts'; import { useFarm } from '../context/FarmContext.tsx'; import { Banner, EmptyFarm, Spinner } from './ui/Feedback.tsx'; import { addDaysIso, cycleDayForDate, formatDateId, resolveCycleStatus, todayIso } from '../utils/format.ts'; import { buildFeedSackInsightContext } from '../utils/insightContextBuilders.ts'; import { PageAiInsight } from './shared/PageAiInsight.tsx'; import FeedStats from './feedSack/FeedStats.tsx'; import FeedSaldoCard from './feedSack/FeedSaldoCard.tsx'; import FeedTrendChart from './feedSack/FeedTrendChart.tsx'; import FeedDailySummaryTable from './feedSack/FeedDailySummaryTable.tsx'; import InitialBalanceCard from './feedSack/InitialBalanceCard.tsx'; const FeedSackCountingPage: React.FC<{ onSettings: () => void }> = ({ onSettings }) => { const { selectedCycle, selectedKandang, selectedSite, cycles, setSelectedCycleId } = useFarm(); const [rows, setRows] = useState([]); const [manualRows, setManualRows] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [showCyclePicker, setShowCyclePicker] = useState(false); const reload = useCallback(async (cycleId: number) => { setLoading(true); setError(null); try { const [karungRows, manualList] = await Promise.all([ api.karungs.list({ cycle_id: cycleId }), api.manualInputs.list({ cycle_id: cycleId }), ]); setRows(karungRows); setManualRows(manualList); } catch (err) { setError(errorMessage(err)); } finally { setLoading(false); } }, []); useEffect(() => { if (!selectedCycle) { setRows([]); setManualRows([]); return; } void reload(selectedCycle.id); }, [selectedCycle, reload]); const sortedRows = useMemo(() => [...rows].sort((a, b) => a.date.localeCompare(b.date)), [rows]); const latest = sortedRows[sortedRows.length - 1] ?? null; const sortedManualRows = useMemo( () => [...manualRows].sort((a, b) => a.date.localeCompare(b.date)), [manualRows] ); const visibleThrough = selectedCycle?.visible_through_date; // Display date follows the latest IoT snapshot; without any snapshot, fall back to today. // Manual cards only use a same-date row — never fall back to an older day. const displayDate = latest?.date ?? visibleThrough ?? todayIso(); const latestManual = sortedManualRows.find((row) => row.date === displayDate) ?? null; const currentDay = useMemo(() => { if (!selectedCycle) return 0; const rawDay = cycleDayForDate(selectedCycle.start_date, displayDate); return Math.min(rawDay, Math.max(0, selectedCycle.total_days - 1)); }, [selectedCycle, displayDate]); const sortedCycles = useMemo(() => { return [...cycles].sort( (a, b) => new Date(b.start_date).getTime() - new Date(a.start_date).getTime() ); }, [cycles]); const formatCycleDateRange = (cycle: Cycle) => { const start = formatDateId(cycle.start_date); if (cycle.end_date) return `${start} - ${formatDateId(cycle.end_date)}`; return `${start} - ${formatDateId(addDaysIso(cycle.start_date, cycle.total_days))}`; }; const lastUpdated = useMemo(() => { if (!latest) return null; const timestamp = latest.updated_at || latest.created_at; if (!timestamp) return null; const date = new Date(timestamp); if (Number.isNaN(date.getTime())) return null; return `${date.toLocaleDateString('id-ID', { day: 'numeric', month: 'short', year: 'numeric', })} ${date.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' })}`; }, [latest]); const insightContext = useMemo(() => { if (!selectedKandang || !selectedCycle) return null; const manualByDate = new Map(sortedManualRows.map((r) => [r.date, r])); const dailyRows = sortedRows.map((r) => { const manual = manualByDate.get(r.date); return { date: r.date, day: cycleDayForDate(selectedCycle.start_date, r.date), masuk: r.in_today, dituang: r.feed_use_today, keluar: r.out_today, masukManual: manual?.feed_in_manual ?? 0, dituangManual: manual?.feed_use_manual ?? 0, }; }); return buildFeedSackInsightContext({ kandangId: selectedKandang.id, kandangName: selectedKandang.kandang_name, cycleId: selectedCycle.id, totalDays: selectedCycle.total_days, dailyRows, saldo_awal_karung: selectedCycle.feed_initial_balance ?? null, total_karung_masuk_iot: latest?.in_total ?? null, total_karung_dituang_iot: latest?.feed_use_total ?? null, total_karung_keluar_iot: latest?.out_total ?? null, total_karung_masuk_manual: latestManual?.feed_in_manual_total ?? null, total_karung_dituang_manual: latestManual?.feed_use_manual_total ?? null, }); }, [selectedKandang, selectedCycle, sortedRows, sortedManualRows, latest, latestManual]); if (!selectedCycle) return ; const cycleStatus = resolveCycleStatus(selectedCycle); return (
{/* Page Title */}

Hitung Karung

Monitoring karung masuk dan karung dituang per hari

{lastUpdated && (

Terakhir diperbarui: {lastUpdated}

)}
{/* Cycle Selector */}
{/* Cycle Picker Dropdown */} {showCyclePicker && (
{sortedCycles.map((cycle) => { const itemStatus = resolveCycleStatus(cycle); return ( ); })}
)}
{error && (
{error}
)} {loading ? ( ) : (
{selectedKandang && insightContext ? ( ) : (

AI Insight Hitung Karung

Pilih kandang untuk melihat AI Insight.
)} {/* Saldo Awal */} {/* Ringkasan stats */} {/* Saldo Karung */} {/* Tren harian */} {/* Rincian harian */}
)}
Sumber data: site dashboard (mirror) · {selectedSite ? selectedSite.site_name : ''} {selectedKandang ? ` · ${selectedKandang.kandang_name}` : ''}
); }; export default FeedSackCountingPage;