98 lines
3.3 KiB
TypeScript
98 lines
3.3 KiB
TypeScript
import React from 'react';
|
|
import { formatNumber } from '../../utils/format.ts';
|
|
import { formatChillFactor, type ExperienceCalc } from './chillFactor.ts';
|
|
|
|
const DEGREE_C = '\u00B0C';
|
|
|
|
const oneDecimal = (value: number | null): string =>
|
|
value === null ? '--' : formatNumber(value, 1);
|
|
|
|
const formatSigned = (value: number | null): string => {
|
|
if (value === null) return '--';
|
|
const sign = value > 0 ? '+' : '';
|
|
return `${sign}${oneDecimal(value)} ${DEGREE_C}`;
|
|
};
|
|
|
|
export interface IotPanelExperienceTempCardProps {
|
|
calc: ExperienceCalc;
|
|
apiExperienceTemp: number | null | undefined;
|
|
}
|
|
|
|
const IotPanelExperienceTempCard: React.FC<IotPanelExperienceTempCardProps> = ({
|
|
calc,
|
|
apiExperienceTemp,
|
|
}) => {
|
|
const calculationRows = [
|
|
{
|
|
label: 'Kecepatan angin',
|
|
value: calc.windSpeed === null ? '--' : `${oneDecimal(calc.windSpeed)} m/s`,
|
|
note: 'Dari sensor angin kandang',
|
|
},
|
|
{
|
|
label: 'Chill factor',
|
|
value: formatChillFactor(calc.chillFactor),
|
|
note: 'Berdasarkan grafik chill factor',
|
|
},
|
|
{
|
|
label: 'Koreksi RH',
|
|
value: formatSigned(calc.humidityAdjustment),
|
|
note: '(RH - 70) / 5',
|
|
},
|
|
{
|
|
label: 'Suhu rata-rata',
|
|
value: calc.averageTemp === null ? '--' : `${oneDecimal(calc.averageTemp)} ${DEGREE_C}`,
|
|
note: 'Suhu rata-rata dari Panel',
|
|
},
|
|
{
|
|
label: 'Chill effect',
|
|
value: formatSigned(calc.chillEffect),
|
|
note: '(Angin x chill factor) - koreksi RH',
|
|
},
|
|
];
|
|
|
|
const apiDiffers =
|
|
apiExperienceTemp != null &&
|
|
calc.experienceTemp != null &&
|
|
Math.abs(apiExperienceTemp - calc.experienceTemp) > 0.05;
|
|
|
|
return (
|
|
<article className="rounded-xl border border-sky-200 bg-gradient-to-br from-sky-50 to-blue-50 p-4 shadow-sm">
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div>
|
|
<h2 className="text-base font-semibold text-slate-800">
|
|
<i className="fa-solid fa-temperature-half mr-2 text-sky-600" />
|
|
Experience Suhu
|
|
</h2>
|
|
<p className="mt-1 text-xs text-slate-500">Suhu yang dirasakan oleh ayam.</p>
|
|
</div>
|
|
<p className="shrink-0 text-3xl font-bold text-sky-600">
|
|
{calc.experienceTemp === null ? 'N/A' : `${oneDecimal(calc.experienceTemp)} ${DEGREE_C}`}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
{calculationRows.map((item) => (
|
|
<div key={item.label} className="rounded-lg border border-sky-100 bg-white/70 p-2">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<span className="text-xs font-medium text-slate-500">{item.label}</span>
|
|
<strong className="text-sm text-slate-800">{item.value}</strong>
|
|
</div>
|
|
<p className="mt-1 text-[11px] text-slate-400">{item.note}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-3 rounded-lg bg-white/60 px-3 py-2 text-xs text-slate-500">
|
|
Experience suhu = Suhu rata-rata - chill effect.
|
|
{apiDiffers && apiExperienceTemp != null && (
|
|
<span className="mt-1 block">
|
|
Nilai tercatat Panel: {oneDecimal(apiExperienceTemp)} {DEGREE_C}.
|
|
</span>
|
|
)}
|
|
</div>
|
|
</article>
|
|
);
|
|
};
|
|
|
|
export default IotPanelExperienceTempCard;
|