import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { api, errorMessage } from '../services/apiClient.ts'; import type { AIInsight, 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, todayIso } from '../utils/format.ts'; 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, updateCycle } = useFarm(); const [rows, setRows] = useState([]); const [manualRows, setManualRows] = useState([]); const [insights, setInsights] = useState([]); const [loading, setLoading] = useState(false); const [syncing, setSyncing] = useState(false); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); const [showCyclePicker, setShowCyclePicker] = useState(false); const reload = useCallback(async (cycleId: number) => { setLoading(true); setError(null); try { const [karungRows, insightRows, manualList] = await Promise.all([ api.karungs.list({ cycle_id: cycleId }), api.insights.list({ cycle_id: cycleId }), api.manualInputs.list({ cycle_id: cycleId }), ]); setRows(karungRows); setInsights(insightRows); setManualRows(manualList); } catch (err) { setError(errorMessage(err)); } finally { setLoading(false); } }, []); useEffect(() => { if (!selectedCycle) { setRows([]); setInsights([]); 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; const displayDateIso = latest?.date ?? visibleThrough ?? todayIso(); const latestManual = sortedManualRows.find((row) => row.date === displayDateIso) ?? sortedManualRows[sortedManualRows.length - 1] ?? null; // Display date follows the latest snapshot; without any snapshot, fall back to today. // Cycle day is capped so data never extends past the cycle's planned duration. const displayDate = latest?.date ?? visibleThrough ?? todayIso(); 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 handleSync = useCallback(async () => { if (!selectedCycle) return; setSyncing(true); setError(null); setNotice(null); try { await api.karungs.request({ cycle_id: selectedCycle.id, section: 'today' }); setNotice('Sinkron karung-web-admin berhasil.'); await reload(selectedCycle.id); } catch (err) { setError(errorMessage(err)); } finally { setSyncing(false); } }, [selectedCycle, reload]); 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]); if (!selectedCycle) return ; 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) => ( ))}
)}
{error && (
{error}
)} {notice && (
{notice}
)} {loading ? ( ) : (
{/* AI Insight */}

AI Insight Hitung Karung

{insights.length === 0 ? ( Belum ada insight untuk siklus ini. ) : (
{insights.slice(0, 4).map((item) => (

{item.section}

{item.alert || 'Insight'}

{item.session}

{item.insight_text}

))}
)}
{/* Saldo Awal */} { updateCycle(saved.cycle, { feed_initial_balance: saved.feed_initial_balance }); void reload(selectedCycle.id); }} /> {/* Ringkasan stats */} {/* Saldo Karung */} {/* Tren harian */} {/* Rincian harian */}
)}
Sumber data: karung-web-admin · {selectedSite ? selectedSite.site_name : ''} {selectedKandang ? ` · ${selectedKandang.kandang_name}` : ''}
); }; export default FeedSackCountingPage;