refactor(FE): Refactor Uniformity charts to use API data

This commit is contained in:
rstubryan
2026-01-06 08:36:23 +07:00
parent c24aebe02d
commit 24499d110a
3 changed files with 125 additions and 100 deletions
@@ -4,89 +4,20 @@ import UniformityBarChart from '@/components/pages/production/uniformity/chart/U
import UniformityGaugeChart from '@/components/pages/production/uniformity/chart/UniformityGaugeChart';
import UniformityBarChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityBarChartSkeleton';
import UniformityGaugeChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton';
import { UniformityDetailItem } from '@/types/api/production/uniformity';
interface BarChartData {
name: string;
uv: number;
isIdeal?: boolean;
}
interface GaugeChartData {
value: number;
label: string;
kandang?: string;
week?: string;
currentValue?: number;
totalValue?: number;
}
import {
UniformityDetailItem,
Uniformity,
} from '@/types/api/production/uniformity';
interface UniformityChartProps {
barChartData?: BarChartData[];
gaugeChartData?: GaugeChartData;
uniformityData?: Uniformity | null;
uniformityDetails?: UniformityDetailItem[];
}
const UniformityChart = ({
barChartData: initialBarChartData,
gaugeChartData: initialGaugeChartData,
uniformityData,
uniformityDetails,
}: UniformityChartProps) => {
const defaultBarChartData: BarChartData[] = [
{
name: '48-52',
uv: 80,
},
{
name: '52-56',
uv: 120,
},
{
name: '56-60',
uv: 160,
},
{
name: '60-64',
uv: 200,
},
{
name: '64-68',
uv: 160,
},
{
name: '68-72',
uv: 120,
},
{
name: '72-76',
uv: 80,
},
{
name: '76-80',
uv: 120,
},
{
name: '84-88',
uv: 160,
},
{
name: '88-92',
uv: 200,
},
{
name: '92-96',
uv: 160,
},
];
const defaultGaugeChartData: GaugeChartData = {
value: 52,
label: 'Uniformity',
week: 'Week 2',
currentValue: 512,
totalValue: 1024,
};
const defaultUniformityDetails: UniformityDetailItem[] = [
{ id: 1, weight: 61, range: 'Ideal' },
{ id: 2, weight: 62, range: 'Ideal' },
@@ -100,34 +31,63 @@ const UniformityChart = ({
const detailsToUse = uniformityDetails || defaultUniformityDetails;
const barChartData = useMemo(() => {
const dataToProcess = initialBarChartData || defaultBarChartData;
if (!detailsToUse || detailsToUse.length === 0) {
return dataToProcess;
if (!uniformityData) {
return [];
}
return dataToProcess.map((bar) => {
const rangeMatch = bar.name.match(/(\d+)-(\d+)/);
if (!rangeMatch) return bar;
if (!detailsToUse || detailsToUse.length === 0) {
return [];
}
const minWeight = parseInt(rangeMatch[1], 10);
const maxWeight = parseInt(rangeMatch[2], 10);
const weights = detailsToUse.map((d) => d.weight);
const minWeight = Math.floor(Math.min(...weights) / 5) * 5;
const maxWeight = Math.ceil(Math.max(...weights) / 5) * 5;
const hasIdealWeight = detailsToUse.some((detail) => {
const weight = detail.weight;
return (
detail.range === 'Ideal' && weight >= minWeight && weight <= maxWeight
);
});
const rangeSize = maxWeight - minWeight < 11 ? 4 : 5;
const ranges: string[] = [];
for (let start = minWeight; start <= maxWeight; start += rangeSize) {
const end = start + rangeSize;
ranges.push(`${start}-${end}`);
}
const totalIdealCount = detailsToUse.filter(
(d) => d.range === 'Ideal'
).length;
return ranges.map((range) => {
const [minStr, maxStr] = range.split('-').map(Number);
const min = minStr;
const max = maxStr;
const birdsInRange = detailsToUse.filter(
(d) => d.weight >= min && d.weight < max
).length;
const hasIdeal = detailsToUse.some(
(d) => d.range === 'Ideal' && d.weight >= min && d.weight < max
);
return {
...bar,
isIdeal: hasIdealWeight,
name: range,
uv: birdsInRange,
isIdeal: hasIdeal,
idealCount: hasIdeal ? totalIdealCount : undefined,
};
});
}, [initialBarChartData, detailsToUse]);
}, [uniformityData, detailsToUse]);
const gaugeChartData = initialGaugeChartData || defaultGaugeChartData;
const gaugeChartData = useMemo(() => {
if (!uniformityData) return undefined;
return {
value: uniformityData.uniformity,
label: 'Uniformity',
week: `Week ${uniformityData.week}`,
currentValue: uniformityData.uniform_qty,
totalValue: uniformityData.chick_qty_of_weight,
};
}, [uniformityData]);
return (
<section className='w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-4 gap-4'>
@@ -140,14 +100,14 @@ const UniformityChart = ({
}}
>
<div className='w-full h-full flex items-center justify-center'>
{barChartData.length === 0 ? (
{!uniformityData || barChartData.length === 0 ? (
<UniformityBarChartSkeleton />
) : (
<UniformityBarChart data={barChartData} />
)}
</div>
</Card>
{gaugeChartData && gaugeChartData.value === 0 ? (
{!uniformityData || !gaugeChartData ? (
<Card
variant='bordered'
title='Weekly Performance ⓘ'
@@ -158,7 +118,7 @@ const UniformityChart = ({
>
<UniformityGaugeChartSkeleton />
</Card>
) : gaugeChartData ? (
) : (
<Card
variant='bordered'
title='Weekly Performance ⓘ'
@@ -175,7 +135,7 @@ const UniformityChart = ({
totalValue={gaugeChartData.totalValue}
/>
</Card>
) : null}
)}
</section>
);
};
@@ -13,6 +13,7 @@ import { UniformityApi } from '@/services/api/uniformity';
import {
DetailOptionType,
type Uniformity,
type UniformityDetail,
} from '@/types/api/production/uniformity';
import { isResponseSuccess } from '@/lib/api-helper';
import { type BaseApiResponse } from '@/types/api/api-general';
@@ -79,7 +80,7 @@ const UniformityConfirmationPreview = ({
{
id: 'file-uniformity',
label: 'File Uniformity',
value: '-', // File name tidak tersedia di GET ALL response
value: '-',
},
{
id: 'status',
@@ -136,6 +137,51 @@ const UniformityConfirmationPreview = ({
);
};
const UniformityChartWrapper = ({
uniformitySwrKey,
}: {
uniformitySwrKey: string;
}) => {
const { data: uniformities } = useSWR(
uniformitySwrKey,
UniformityApi.getAllFetcher
);
const uniformityData = useMemo(() => {
if (isResponseSuccess(uniformities) && uniformities?.data?.length > 0) {
return uniformities.data[0];
}
return null;
}, [uniformities]);
const shouldFetchDetails = !!uniformityData;
const uniformityDetailSwrKey = useMemo(() => {
if (!uniformityData) return null;
return `${UniformityApi.basePath}/${uniformityData.id}?with_details=true`;
}, [uniformityData]);
const { data: uniformityDetailResponse } = useSWR(
uniformityDetailSwrKey,
shouldFetchDetails ? UniformityApi.getAllFetcher : null
);
const uniformityDetails = useMemo(() => {
if (shouldFetchDetails && isResponseSuccess(uniformityDetailResponse)) {
const detailData =
uniformityDetailResponse.data as unknown as UniformityDetail;
return detailData.uniformity_details;
}
return undefined;
}, [shouldFetchDetails, uniformityDetailResponse]);
return (
<UniformityChart
uniformityData={uniformityData}
uniformityDetails={uniformityDetails}
/>
);
};
const UniformityTable = () => {
const router = useRouter();
const searchParams = useSearchParams();
@@ -830,7 +876,7 @@ const UniformityTable = () => {
<div className='my-4 divider'></div>
<section>
<UniformityChart />
<UniformityChartWrapper uniformitySwrKey={uniformitySwrKey} />
</section>
<Card
@@ -27,6 +27,7 @@ interface BarChartData {
name: string;
uv: number;
isIdeal?: boolean;
idealCount?: number;
}
interface UniformityBarChartProps {
@@ -35,7 +36,25 @@ interface UniformityBarChartProps {
function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
if (active && payload && payload.length && label !== undefined) {
const data = payload[0] as unknown as { payload: BarChartData };
const chartData = data.payload as BarChartData;
const labelStr = String(label);
if (chartData.isIdeal && chartData.idealCount !== undefined) {
return (
<div className='bg-[#18181B] p-2.5 shadow-sm text-white rounded-2xl rounded-bl-none'>
<p className='m-0 font-bold text-white/50'>Uniformity 2025</p>
<div className='flex items-center gap-2 mt-2 justify-between'>
<div className='flex items-center gap-2'>
<div className='w-5 h-5 bg-[#0069E0] rounded-md'></div>
{chartData.idealCount} of Birds
</div>
<span>{labelStr}</span>
</div>
</div>
);
}
return (
<div className='bg-[#18181B] p-2.5 shadow-sm text-white rounded-2xl rounded-bl-none'>
<p className='m-0 font-bold text-white/50'>Uniformity 2025</p>