import React, { useEffect, useMemo, useState } from 'react'; import { api, ApiError, errorMessage } from '../services/apiClient.ts'; import type { AIInsight, ChickenCounting, ChickenWeight, Karung, KPI, LatestAvg, } from '../types/api.ts'; import type { View } from '../types/common.ts'; import { useFarm } from '../context/FarmContext.tsx'; import KpiCard, { type KpiCardModel } from './KpiCard.tsx'; import { Banner, EmptyFarm, PageHeader, Spinner } from './ui/Feedback.tsx'; import { formatNumber, formatDateRange } from '../utils/format.ts'; import DashboardReportCtas from './dashboard/DashboardReportCtas.tsx'; import DashboardManualInputForm from './dashboard/DashboardManualInputForm.tsx'; import ReportPreviewOverlay from './dashboard/ReportPreviewOverlay.tsx'; import type { DashboardReportType } from '../types/dashboardReport.ts'; type Props = { setView: (view: View) => void }; function chronological(rows: T[]): T[] { return [...rows].reverse(); } const Dashboard: React.FC = ({ setView }) => { const { selectedCycle, selectedKandang, selectedSite } = useFarm(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [kpis, setKpis] = useState([]); const [countings, setCountings] = useState([]); const [weights, setWeights] = useState([]); const [karung, setKarung] = useState(null); const [latestAvg, setLatestAvg] = useState(null); const [insights, setInsights] = useState([]); const [reportOpen, setReportOpen] = useState(false); const [reportType, setReportType] = useState('daily'); useEffect(() => { if (!selectedCycle) { setKpis([]); setCountings([]); setWeights([]); setKarung(null); setLatestAvg(null); setInsights([]); return; } const cycleId = selectedCycle.id; let cancelled = false; (async () => { setLoading(true); setError(null); try { const [kpiRows, countingRows, weightRows, insightRows] = await Promise.all([ api.kpis.list({ cycle_id: cycleId }), api.countings.list({ cycle_id: cycleId }), api.weights.list({ cycle_id: cycleId }), api.insights.list({ cycle_id: cycleId }), ]); let latestKarung: Karung | null = null; try { latestKarung = await api.karungs.latest({ cycle_id: cycleId }); } 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); setWeights(weightRows); setKarung(latestKarung); setLatestAvg(avg); setInsights(insightRows); } catch (err) { if (!cancelled) setError(errorMessage(err)); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, [selectedCycle]); const cards: KpiCardModel[] = useMemo(() => { const latest = kpis[0]; const fcrTrend = chronological(kpis).map((row) => row.fcr); const eefTrend = chronological(kpis).map((row) => row.eef); const popTrend = chronological(countings).map((row) => row.total_count); const weightTrend = chronological(weights).map((row) => row.average_weight); const age = latest?.age ?? selectedCycle?.current_day ?? null; return [ { key: 'umur', title: 'Umur', value: age, unit: 'hari', icon: '๐Ÿ“…', status: age == null ? 'na' : 'ok', }, { key: 'populasi', title: 'Populasi', value: latest?.chicken_life ?? countings[0]?.total_count ?? null, unit: 'ekor', icon: '๐Ÿ”', trend: popTrend, status: 'ok', targetView: 'dataManagement', }, { key: 'karung', title: 'Karung Dituang', value: karung?.out_today ?? karung?.feed_use_today ?? null, unit: 'karung', icon: '๐Ÿ›ข๏ธ', status: karung ? 'ok' : 'na', targetView: 'feedSackCounting', }, { key: 'weight', title: 'Berat AVG', value: latestAvg?.average_weight ?? weights[0]?.average_weight ?? null, unit: 'gram', icon: 'โš–๏ธ', trend: weightTrend, status: latestAvg || weights[0] ? 'ok' : 'na', targetView: 'weightMonitoring', }, { key: 'fcr', title: 'FCR', value: latest?.fcr ?? null, unit: '', icon: '๐Ÿ“ˆ', trend: fcrTrend, status: latest ? 'ok' : 'na', targetView: 'fcrDashboard', }, { key: 'eef', title: 'EEF', value: latest?.eef ?? null, unit: '', icon: '๐ŸŽฏ', trend: eefTrend, status: latest ? 'ok' : 'na', targetView: 'eefDashboard', }, ]; }, [kpis, countings, weights, karung, latestAvg, selectedCycle]); if (!selectedCycle) return setView('kandangSettings')} />; const feedTotal = kpis[0]?.feed_total ?? 0; const feedWeightKg = feedTotal * 50; const feedInitialBalance = selectedCycle?.feed_initial_balance ?? 0; const feedInTotal = karung?.in_total ?? feedInitialBalance; const feedOutTotal = karung?.out_total ?? 0; const feedSaldo = feedInTotal - feedTotal - feedOutTotal; const feedSaldoColor = feedSaldo > 0 ? 'text-green-600' : feedSaldo < 0 ? 'text-red-600' : 'text-gray-600'; return (
{ setReportType('daily'); setReportOpen(true); }} onOpenEndCycle={() => { setReportType('end_cycle'); setReportOpen(true); }} />
{error && (
{error}
)} {loading ? ( ) : ( <>
{cards.map((card) => ( setView(card.targetView as View) : undefined} /> ))}

Total Pakan

Kumulatif karung pakan sejak awal siklus

Total Pakan

{formatNumber(feedTotal)}

karung

Total Feed In

{formatNumber(feedInTotal)}

karung

Total Feed Out

{formatNumber(feedOutTotal)}

karung

Saldo Awal

{formatNumber(feedInitialBalance)}

karung

Total Berat Pakan

{formatNumber(feedWeightKg)}

kg

Saldo Karung Pakan

{formatNumber(feedSaldo)}

karung

AI Insight

{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}

))}
)}
)} setReportOpen(false)} />
); }; export default Dashboard;