first commit
This commit is contained in:
commit
367533c02b
171 files changed
+101175
No files matched your search
@@ -0,0 +1,145 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { Karung, ManualInput } from '../../types/api.ts';
|
||||
import { formatNumber } from '../../utils/format.ts';
|
||||
|
||||
const StatItem: React.FC<{
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
value: number | null;
|
||||
colorOverride?: string;
|
||||
}> = ({ icon, label, value, colorOverride }) => (
|
||||
<div className="border border-gray-200 rounded-lg p-3 hover:border-gray-300 transition-colors">
|
||||
<div className="flex items-start gap-2 mb-3 min-w-0">
|
||||
<div className="text-gray-500">{icon}</div>
|
||||
<p className="min-w-0 truncate text-xs sm:text-sm font-medium text-gray-700" title={label}>
|
||||
{label}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-md bg-gray-50/70 p-3">
|
||||
<p
|
||||
className="text-xl sm:text-2xl font-bold leading-none"
|
||||
style={{ color: colorOverride ?? '#374151' }}
|
||||
>
|
||||
{value == null ? 'N/A' : formatNumber(value)}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-0.5">karung</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface FeedStatsProps {
|
||||
latest: Karung | null;
|
||||
latestManual: ManualInput | null;
|
||||
cycleStartDate: string;
|
||||
displayDate: string;
|
||||
}
|
||||
|
||||
const FeedStats: React.FC<FeedStatsProps> = ({
|
||||
latest,
|
||||
latestManual,
|
||||
cycleStartDate,
|
||||
displayDate,
|
||||
}) => {
|
||||
const formatDisplayDate = (iso: string) =>
|
||||
new Intl.DateTimeFormat('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
}).format(new Date(`${iso}T00:00:00`));
|
||||
|
||||
const todayLabel = useMemo(() => formatDisplayDate(displayDate), [displayDate]);
|
||||
|
||||
const totalCycleLabel = useMemo(() => {
|
||||
const start = new Intl.DateTimeFormat('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
}).format(new Date(`${cycleStartDate}T00:00:00`));
|
||||
const end = new Intl.DateTimeFormat('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
}).format(new Date(`${displayDate}T00:00:00`));
|
||||
return `Total (${start} - ${end})`;
|
||||
}, [cycleStartDate, displayDate]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: 'masuk',
|
||||
title: 'Karung Masuk',
|
||||
color: '#2563eb',
|
||||
icon: <i className="fa-solid fa-arrow-down text-blue-600"></i>,
|
||||
iotToday: latest?.in_today ?? null,
|
||||
iotTotal: latest?.in_total ?? null,
|
||||
manualToday: latestManual?.feed_in_manual ?? null,
|
||||
manualTotal: latestManual?.feed_in_manual_total ?? null,
|
||||
},
|
||||
{
|
||||
key: 'dituang',
|
||||
title: 'Karung Dituang',
|
||||
color: '#ea580c',
|
||||
icon: <i className="fa-solid fa-arrow-up text-orange-600"></i>,
|
||||
iotToday: latest?.feed_use_today ?? null,
|
||||
iotTotal: latest?.feed_use_total ?? null,
|
||||
manualToday: latestManual?.feed_use_manual ?? null,
|
||||
manualTotal: latestManual?.feed_use_manual_total ?? null,
|
||||
},
|
||||
{
|
||||
key: 'keluar',
|
||||
title: 'Karung Keluar',
|
||||
color: '#dc2626',
|
||||
icon: <i className="fa-solid fa-arrow-right text-red-600"></i>,
|
||||
iotToday: latest?.out_today ?? null,
|
||||
iotTotal: latest?.out_total ?? null,
|
||||
manualToday: latestManual?.feed_out_manual ?? null,
|
||||
manualTotal: latestManual?.feed_out_manual_total ?? null,
|
||||
},
|
||||
],
|
||||
[latest, latestManual]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-white p-4 sm:p-6 rounded-xl shadow-sm">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 sm:gap-6">
|
||||
{columns.map((col) => (
|
||||
<div key={col.key}>
|
||||
<h3 className="font-semibold text-sm sm:text-base mb-2" style={{ color: col.color }}>
|
||||
{col.title}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-3">
|
||||
<StatItem
|
||||
icon={<i className="fa-solid fa-microchip text-purple-600"></i>}
|
||||
label={`IOT · ${todayLabel}`}
|
||||
value={col.iotToday}
|
||||
colorOverride={col.color}
|
||||
/>
|
||||
<StatItem
|
||||
icon={<i className="fa-solid fa-microchip text-purple-600"></i>}
|
||||
label={`IOT · ${totalCycleLabel}`}
|
||||
value={col.iotTotal}
|
||||
colorOverride={col.color}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<StatItem
|
||||
icon={<i className="fa-solid fa-user-pen text-gray-500"></i>}
|
||||
label={`Manual · ${todayLabel}`}
|
||||
value={col.manualToday}
|
||||
colorOverride="#6b7280"
|
||||
/>
|
||||
<StatItem
|
||||
icon={<i className="fa-solid fa-user-pen text-gray-500"></i>}
|
||||
label={`Manual · ${totalCycleLabel}`}
|
||||
value={col.manualTotal}
|
||||
colorOverride="#6b7280"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedStats;
|
||||
Reference in new issue
Block a user