Files
dashboard-cpsp-executive/components/dashboard/KandangRankingTop5.tsx
T
2026-09-08 15:21:23 +07:00

117 lines
3.7 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { errorMessage } from '../../services/apiClient.ts';
import type { View } from '../../types/common.ts';
import { useFarm } from '../../context/FarmContext.tsx';
import { Banner, Spinner } from '../ui/Feedback.tsx';
import { formatRatio } from '../../utils/format.ts';
import {
loadKandangRanking,
type KandangRankingRow,
} from '../../utils/kandangRanking.ts';
type Props = {
setView: (view: View) => void;
};
const KandangRankingTop5: React.FC<Props> = ({ setView }) => {
const {
selectedSite,
setSelectedSiteId,
setSelectedKandangId,
setSelectedCycleId,
} = useFarm();
const [rows, setRows] = useState<KandangRankingRow[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const openEef = (row: KandangRankingRow) => {
setSelectedSiteId(row.siteId);
setSelectedKandangId(row.kandangId);
setSelectedCycleId(row.cycleId);
setView('eefDashboard');
};
useEffect(() => {
if (!selectedSite) {
setRows([]);
setError(null);
return;
}
let cancelled = false;
(async () => {
setLoading(true);
setError(null);
try {
const next = await loadKandangRanking({ sites: [selectedSite], months: 3 });
if (!cancelled) setRows(next.slice(0, 5));
} catch (err) {
if (!cancelled) {
setError(errorMessage(err));
setRows([]);
}
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => {
cancelled = true;
};
}, [selectedSite]);
if (!selectedSite) return null;
return (
<section className="mt-8 bg-white rounded-xl shadow-sm border border-gray-100 p-5">
<div className="flex flex-wrap items-start justify-between gap-3 mb-4">
<div>
<h2 className="text-lg font-bold text-gray-800">Peringkat EEF</h2>
<p className="text-xs text-gray-500 mt-0.5">
Top 5 · {selectedSite.site_name} · 3 bulan terakhir (siklus tertutup)
</p>
</div>
<button
type="button"
className="text-sm font-semibold text-red-600 hover:text-red-700"
onClick={() => setView('kandangRanking')}
>
Lihat semua
</button>
</div>
{error && <Banner kind="error">{error}</Banner>}
{loading ? (
<Spinner />
) : rows.length === 0 ? (
<p className="text-sm text-gray-500">Tidak ada siklus tertutup dalam 3 bulan terakhir.</p>
) : (
<ul className="divide-y divide-gray-100">
{rows.map((row) => (
<li
key={row.cycleId}
className="py-3 flex items-center justify-between gap-3 cursor-pointer hover:bg-gray-50 -mx-2 px-2 rounded-lg"
onClick={() => openEef(row)}
>
<div className="flex items-center gap-3 min-w-0">
<span className="w-7 h-7 rounded-full bg-red-50 text-red-700 text-xs font-bold flex items-center justify-center flex-shrink-0">
{row.rank > 0 ? row.rank : '—'}
</span>
<div className="min-w-0">
<p className="text-sm font-semibold text-gray-800 truncate">{row.kandangName}</p>
<p className="text-xs text-gray-500">
FCR {row.fcr == null ? 'N/A' : formatRatio(row.fcr, 3)}
</p>
</div>
</div>
<p className="text-sm font-bold text-gray-900 flex-shrink-0">
{row.eef == null ? 'N/A' : formatRatio(row.eef, 3)}
</p>
</li>
))}
</ul>
)}
</section>
);
};
export default KandangRankingTop5;