adjust eef, panel iot, and main dashboard page ai insight shown data
This commit is contained in:
1 parent
73bdb8c739
commit
0d960c8153
5 files changed
+130
-22
No files matched your search
@@ -74,6 +74,20 @@ const renderValue = (val: unknown, suffix: string = ''): string => {
|
||||
return `${val} ${suffix}`.trim();
|
||||
};
|
||||
|
||||
/** Format a day's min–max range for IoT panel insight metrics (no rounding). */
|
||||
const renderDayRange = (minVal: unknown, maxVal: unknown, suffix: string): string => {
|
||||
const lo = typeof minVal === 'number' && Number.isFinite(minVal) ? minVal : null;
|
||||
const hi = typeof maxVal === 'number' && Number.isFinite(maxVal) ? maxVal : null;
|
||||
// Keep full precision — do not toFixed / cap fraction digits.
|
||||
const fmt = (n: number) =>
|
||||
n.toLocaleString('id-ID', { maximumFractionDigits: 20, useGrouping: false });
|
||||
if (lo === null && hi === null) return 'N/A';
|
||||
if (lo === null) return `${fmt(hi!)} ${suffix}`.trim();
|
||||
if (hi === null) return `${fmt(lo)} ${suffix}`.trim();
|
||||
if (lo === hi) return `${fmt(lo)} ${suffix}`.trim();
|
||||
return `${fmt(lo)} – ${fmt(hi)} ${suffix}`.trim();
|
||||
};
|
||||
|
||||
/** Match FeedSaldoCard: masuk IOT + saldo awal. */
|
||||
const feedMasukTotal = (ctx: Record<string, unknown>): number | null => {
|
||||
const masuk =
|
||||
@@ -330,24 +344,40 @@ export const PageAiInsight: React.FC<PageAiInsightProps> = ({
|
||||
}
|
||||
|
||||
if (topic === 'iot_panel') {
|
||||
const actual = n(displayContext.suhu_rata_rata_C);
|
||||
const minTemp = n(displayContext.suhu_min_C);
|
||||
const maxTemp = n(displayContext.suhu_max_C);
|
||||
const lastTemp = n(displayContext.suhu_rata_rata_C);
|
||||
const setpoint = n(displayContext.setpoint_C);
|
||||
if (actual === null) return null;
|
||||
const diff = setpoint === null ? null : actual - setpoint;
|
||||
if (minTemp === null && maxTemp === null && lastTemp === null) return null;
|
||||
const lo = minTemp ?? lastTemp!;
|
||||
const hi = maxTemp ?? lastTemp!;
|
||||
// Grade against the farther extreme from setpoint so a bad stretch that day surfaces.
|
||||
const worstDiff =
|
||||
setpoint === null
|
||||
? null
|
||||
: Math.max(Math.abs(lo - setpoint), Math.abs(hi - setpoint));
|
||||
const signedWorst =
|
||||
setpoint === null
|
||||
? null
|
||||
: Math.abs(lo - setpoint) >= Math.abs(hi - setpoint)
|
||||
? lo - setpoint
|
||||
: hi - setpoint;
|
||||
return {
|
||||
label: 'Suhu Rata-rata Kandang',
|
||||
value: actual.toLocaleString('id-ID'),
|
||||
value: renderDayRange(lo, hi, ''),
|
||||
unit: '°C',
|
||||
context: setpoint === null ? null : `Setpoint: ${setpoint.toLocaleString('id-ID')} °C`,
|
||||
status: (diff === null
|
||||
status: (worstDiff === null
|
||||
? 'unknown'
|
||||
: Math.abs(diff) <= 1
|
||||
: worstDiff <= 1
|
||||
? 'ok'
|
||||
: Math.abs(diff) <= 2.5
|
||||
: worstDiff <= 2.5
|
||||
? 'warning'
|
||||
: 'critical') as InsightStatus,
|
||||
deltaLabel:
|
||||
diff === null ? null : `${diff > 0 ? '+' : ''}${diff.toFixed(1)} °C vs setpoint`,
|
||||
signedWorst === null
|
||||
? null
|
||||
: `${signedWorst > 0 ? '+' : ''}${signedWorst} °C vs setpoint`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -811,14 +841,6 @@ export const PageAiInsight: React.FC<PageAiInsightProps> = ({
|
||||
|
||||
{topic === 'eef' && (
|
||||
<>
|
||||
<div className="flex justify-between items-center py-1 border-b border-gray-50">
|
||||
<span className="text-sm text-gray-600 font-medium">EEF Aktual</span>
|
||||
<span className="text-sm font-bold text-gray-900">
|
||||
{typeof displayContext.eef_terakhir === 'number'
|
||||
? formatRatio(displayContext.eef_terakhir, 3)
|
||||
: renderValue(displayContext.eef_terakhir)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-1 border-b border-gray-50">
|
||||
<span className="text-sm text-gray-600 font-medium">FCR Aktual</span>
|
||||
<span className="text-sm font-bold text-gray-900">
|
||||
@@ -830,7 +852,31 @@ export const PageAiInsight: React.FC<PageAiInsightProps> = ({
|
||||
<div className="flex justify-between items-center py-1 border-b border-gray-50">
|
||||
<span className="text-sm text-gray-600 font-medium">Persentase Hidup</span>
|
||||
<span className="text-sm font-bold text-gray-900">
|
||||
{renderValue(displayContext.persen_hidup, '%')}
|
||||
{typeof displayContext.persen_hidup === 'number'
|
||||
? `${displayContext.persen_hidup.toLocaleString('id-ID', {
|
||||
maximumFractionDigits: 2,
|
||||
})} %`
|
||||
: renderValue(displayContext.persen_hidup, '%')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-1 border-b border-gray-50">
|
||||
<span className="text-sm text-gray-600 font-medium">BW Rata-rata Panen</span>
|
||||
<span className="text-sm font-bold text-gray-900">
|
||||
{typeof displayContext.bw_panen_gram === 'number'
|
||||
? `${displayContext.bw_panen_gram.toLocaleString('id-ID', {
|
||||
maximumFractionDigits: 2,
|
||||
})} g`
|
||||
: renderValue(displayContext.bw_panen_gram, 'g')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-1 border-b border-gray-50">
|
||||
<span className="text-sm text-gray-600 font-medium">Umur Rata-rata Panen</span>
|
||||
<span className="text-sm font-bold text-gray-900">
|
||||
{typeof displayContext.umur_panen_rata_hari === 'number'
|
||||
? `${displayContext.umur_panen_rata_hari.toLocaleString('id-ID', {
|
||||
maximumFractionDigits: 2,
|
||||
})} hari`
|
||||
: renderValue(displayContext.umur_panen_rata_hari, 'hari')}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
@@ -838,16 +884,40 @@ export const PageAiInsight: React.FC<PageAiInsightProps> = ({
|
||||
|
||||
{topic === 'iot_panel' && (
|
||||
<>
|
||||
<div className="flex justify-between items-center py-1 border-b border-gray-50">
|
||||
<span className="text-sm text-gray-600 font-medium">Experience Suhu</span>
|
||||
<span className="text-sm font-bold text-gray-900">
|
||||
{renderDayRange(
|
||||
displayContext.experience_suhu_min_C ??
|
||||
displayContext.experience_suhu_C,
|
||||
displayContext.experience_suhu_max_C ??
|
||||
displayContext.experience_suhu_C,
|
||||
'°C'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-1 border-b border-gray-50">
|
||||
<span className="text-sm text-gray-600 font-medium">Kelembapan</span>
|
||||
<span className="text-sm font-bold text-gray-900">
|
||||
{renderValue(displayContext.kelembapan_persen, '%')}
|
||||
{renderDayRange(
|
||||
displayContext.kelembapan_min_persen ??
|
||||
displayContext.kelembapan_persen,
|
||||
displayContext.kelembapan_max_persen ??
|
||||
displayContext.kelembapan_persen,
|
||||
'%'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-1 border-b border-gray-50">
|
||||
<span className="text-sm text-gray-600 font-medium">Kecepatan Angin</span>
|
||||
<span className="text-sm font-bold text-gray-900">
|
||||
{renderValue(displayContext.kecepatan_angin_mPerDetik, 'm/s')}
|
||||
{renderDayRange(
|
||||
displayContext.kecepatan_angin_min_mPerDetik ??
|
||||
displayContext.kecepatan_angin_mPerDetik,
|
||||
displayContext.kecepatan_angin_max_mPerDetik ??
|
||||
displayContext.kecepatan_angin_mPerDetik,
|
||||
'm/s'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in new issue
Block a user