update applying current ai insight from dashboard-cpsp
This commit is contained in:
1 parent
31272035e9
commit
b87f20e3b3
27 files changed
+5300
-343
No files matched your search
@@ -9,10 +9,12 @@ import {
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
import { api, errorMessage } from '../services/apiClient.ts';
|
||||
import type { AIInsight, ChickenWeight, KPI } from '../types/api.ts';
|
||||
import type { ChickenWeight, KPI } from '../types/api.ts';
|
||||
import { useFarm } from '../context/FarmContext.tsx';
|
||||
import { Banner, EmptyFarm, PageHeader, Spinner } from './ui/Feedback.tsx';
|
||||
import { formatNumber, formatRatio } from '../utils/format.ts';
|
||||
import { buildFcrInsightContext } from '../utils/insightContextBuilders.ts';
|
||||
import { PageAiInsight } from './shared/PageAiInsight.tsx';
|
||||
|
||||
type FcrRow = {
|
||||
day: number;
|
||||
@@ -155,10 +157,9 @@ const SupportingMetricChart: React.FC<{
|
||||
);
|
||||
|
||||
const FcrDashboardPage: React.FC<{ onSettings: () => void }> = ({ onSettings }) => {
|
||||
const { selectedCycle } = useFarm();
|
||||
const { selectedCycle, selectedKandang } = useFarm();
|
||||
const [rows, setRows] = useState<KPI[]>([]);
|
||||
const [weightRows, setWeightRows] = useState<ChickenWeight[]>([]);
|
||||
const [insights, setInsights] = useState<AIInsight[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||
@@ -167,13 +168,11 @@ const FcrDashboardPage: React.FC<{ onSettings: () => void }> = ({ onSettings })
|
||||
if (!selectedCycle) return;
|
||||
setError(null);
|
||||
try {
|
||||
const [kpiRows, insightRows, weightList] = await Promise.all([
|
||||
const [kpiRows, weightList] = await Promise.all([
|
||||
api.kpis.list({ cycle_id: selectedCycle.id }),
|
||||
api.insights.list({ cycle_id: selectedCycle.id }),
|
||||
api.weights.list({ cycle_id: selectedCycle.id }),
|
||||
]);
|
||||
setRows(kpiRows);
|
||||
setInsights(insightRows);
|
||||
setWeightRows(weightList);
|
||||
setLastUpdated(new Date());
|
||||
} catch (err) {
|
||||
@@ -185,7 +184,6 @@ const FcrDashboardPage: React.FC<{ onSettings: () => void }> = ({ onSettings })
|
||||
if (!selectedCycle) {
|
||||
setRows([]);
|
||||
setWeightRows([]);
|
||||
setInsights([]);
|
||||
setLastUpdated(null);
|
||||
return;
|
||||
}
|
||||
@@ -258,20 +256,16 @@ const FcrDashboardPage: React.FC<{ onSettings: () => void }> = ({ onSettings })
|
||||
const latestFcr = getLatestValid('fcr');
|
||||
const latestIotWeight = getLatestValid('iotWeight');
|
||||
|
||||
const fcrInsights = useMemo(() => {
|
||||
const matched = insights.filter((item) => {
|
||||
const text =
|
||||
`${item.section ?? ''} ${item.alert ?? ''} ${item.insight_text ?? ''}`.toLowerCase();
|
||||
return (
|
||||
text.includes('fcr') ||
|
||||
text.includes('pakan') ||
|
||||
text.includes('feed') ||
|
||||
text.includes('konversi') ||
|
||||
text.includes('panen')
|
||||
);
|
||||
const insightContext = useMemo(() => {
|
||||
if (!selectedKandang) return null;
|
||||
return buildFcrInsightContext({
|
||||
kandangId: selectedKandang.id,
|
||||
kandangName: selectedKandang.kandang_name,
|
||||
cycleId: selectedCycle?.id,
|
||||
totalDays: selectedCycle?.total_days ?? null,
|
||||
fcrRows,
|
||||
});
|
||||
return (matched.length > 0 ? matched : insights).slice(0, 4);
|
||||
}, [insights]);
|
||||
}, [selectedKandang, selectedCycle, fcrRows]);
|
||||
|
||||
if (!selectedCycle) return <EmptyFarm onSettings={onSettings} />;
|
||||
|
||||
@@ -291,37 +285,56 @@ const FcrDashboardPage: React.FC<{ onSettings: () => void }> = ({ onSettings })
|
||||
</div>
|
||||
)}
|
||||
|
||||
<section>
|
||||
<h2 className="text-lg font-bold text-gray-800 mb-3">
|
||||
<i className="fa-solid fa-brain text-red-600 mr-2" />
|
||||
AI Insight FCR
|
||||
</h2>
|
||||
{loading ? (
|
||||
<Spinner />
|
||||
) : fcrInsights.length === 0 ? (
|
||||
<Banner>Belum ada insight untuk siklus ini.</Banner>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{fcrInsights.map((item) => (
|
||||
<article
|
||||
key={item.id}
|
||||
className="bg-white rounded-xl shadow-sm p-5 border border-gray-100"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-wide text-gray-400">{item.section}</p>
|
||||
<h3 className="font-semibold text-gray-800 mt-1">{item.alert || 'Insight'}</h3>
|
||||
</div>
|
||||
<span className="text-xs text-gray-400">{item.session}</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mt-3 whitespace-pre-wrap">
|
||||
{item.insight_text}
|
||||
</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
{selectedKandang && insightContext ? (
|
||||
<PageAiInsight
|
||||
topic="fcr"
|
||||
cacheKey={`fcr-k${selectedKandang.id}-c${selectedCycle.id}-rows${fcrRows.length}-fcr${String(latestFcr ?? 'null').replace('.', '_')}`}
|
||||
contextData={insightContext}
|
||||
chartSpec={{
|
||||
type: 'trend',
|
||||
historyKey: 'tren_fcr_harian',
|
||||
title: 'Tren FCR',
|
||||
series: [{ field: 'fcr_aktual', label: 'FCR aktual', color: '#16a34a' }],
|
||||
}}
|
||||
scalarScope={{
|
||||
historyKey: 'tren_fcr_harian',
|
||||
derive: {
|
||||
fcrTerakhir: { field: 'fcr_aktual', agg: 'lastValid', replaces: 'latestFcr' },
|
||||
totalPakan_karung: {
|
||||
field: 'total_pakan_karung',
|
||||
agg: 'last',
|
||||
replaces: 'latestFeedTotal',
|
||||
emptyAs: 0,
|
||||
},
|
||||
bobotIot_gram: {
|
||||
field: 'bobot_iot_gram',
|
||||
agg: 'lastValid',
|
||||
replaces: 'latestIotWeight',
|
||||
},
|
||||
ayamHidup_ekor: {
|
||||
field: 'ayam_hidup_ekor',
|
||||
agg: 'last',
|
||||
replaces: 'latestChickenLife',
|
||||
emptyAs: 0,
|
||||
},
|
||||
},
|
||||
drop: [
|
||||
'latestFcr',
|
||||
'latestFeedTotal',
|
||||
'latestIotWeight',
|
||||
'latestChickenLife',
|
||||
],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<section>
|
||||
<h2 className="text-lg font-bold text-gray-800 mb-3">
|
||||
<i className="fa-solid fa-brain text-red-600 mr-2" />
|
||||
AI Insight FCR
|
||||
</h2>
|
||||
<Banner>Pilih kandang untuk melihat AI Insight.</Banner>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<Spinner />
|
||||
|
||||
Reference in new issue
Block a user