Files
dashboard-cpsp/components/shared/InsightConditionChart.tsx
T

106 lines
3.6 KiB
TypeScript

import React from 'react';
export interface ConditionBar {
label: string;
actual: number | null;
standard: number | null;
unit: string;
/** Optional note shown under the bar, e.g. why a standard is absent. */
note?: string;
}
export interface InsightConditionChartProps {
bars: ConditionBar[];
title?: string;
emptyLabel?: string;
}
const AKTUAL = '#dc2626';
const STANDAR = '#3b82f6';
const fmt = (value: number, unit: string) =>
`${Number(value).toLocaleString('id-ID', { maximumFractionDigits: 1 })}${unit ? ` ${unit}` : ''}`;
/**
* Current readings against their CP 707 standard, one row per metric.
* Used when there is no per-day history (e.g. live IoT snapshot).
*/
const InsightConditionChart: React.FC<InsightConditionChartProps> = ({
bars,
title,
emptyLabel = 'Belum ada pembacaan sensor untuk digambarkan.',
}) => {
const usable = bars.filter((bar) => bar.actual !== null || bar.standard !== null);
if (usable.length === 0) {
return (
<div className="rounded-xl border border-gray-100 bg-gray-50/60 p-4 text-center text-sm text-gray-500">
{emptyLabel}
</div>
);
}
return (
<div className="rounded-xl border border-gray-100 bg-white p-4 shadow-sm">
{title && <h4 className="mb-1 text-sm font-bold text-gray-800">{title}</h4>}
<div className="mb-3 flex items-center gap-4 text-xs text-gray-500">
<span className="flex items-center gap-1.5">
<span className="inline-block h-2.5 w-2.5 rounded-sm" style={{ background: AKTUAL }} />
Aktual
</span>
<span className="flex items-center gap-1.5">
<span className="inline-block h-2.5 w-2.5 rounded-sm" style={{ background: STANDAR }} />
Standar CP 707
</span>
</div>
<div className="space-y-3">
{usable.map((bar) => {
const max = Math.max(bar.actual ?? 0, bar.standard ?? 0, 1);
const pct = (value: number | null) =>
value === null ? 0 : Math.max(2, Math.round((value / max) * 100));
return (
<div key={bar.label}>
<div className="mb-1 flex items-baseline justify-between">
<span className="text-xs font-medium text-gray-600">{bar.label}</span>
<span className="text-xs text-gray-500">
{bar.actual === null ? 'data tidak tersedia' : fmt(bar.actual, bar.unit)}
{bar.standard !== null && (
<span className="text-gray-400"> · standar {fmt(bar.standard, bar.unit)}</span>
)}
</span>
</div>
<div className="space-y-1">
<div className="h-2.5 w-full rounded-sm bg-gray-100">
{bar.actual !== null && (
<div
className="h-full rounded-sm"
style={{ width: `${pct(bar.actual)}%`, background: AKTUAL }}
title={`Aktual ${fmt(bar.actual, bar.unit)}`}
/>
)}
</div>
<div className="h-2.5 w-full rounded-sm bg-gray-100">
{bar.standard !== null && (
<div
className="h-full rounded-sm"
style={{ width: `${pct(bar.standard)}%`, background: STANDAR }}
title={`Standar ${fmt(bar.standard, bar.unit)}`}
/>
)}
</div>
</div>
{bar.note && <p className="mt-1 text-[11px] text-gray-400">{bar.note}</p>}
</div>
);
})}
</div>
</div>
);
};
export default InsightConditionChart;