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 UniformityGaugeChart from '@/components/pages/production/uniformity/chart/UniformityGaugeChart';
import UniformityBarChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityBarChartSkeleton'; import UniformityBarChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityBarChartSkeleton';
import UniformityGaugeChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton'; import UniformityGaugeChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton';
import { UniformityDetailItem } from '@/types/api/production/uniformity'; import {
UniformityDetailItem,
interface BarChartData { Uniformity,
name: string; } from '@/types/api/production/uniformity';
uv: number;
isIdeal?: boolean;
}
interface GaugeChartData {
value: number;
label: string;
kandang?: string;
week?: string;
currentValue?: number;
totalValue?: number;
}
interface UniformityChartProps { interface UniformityChartProps {
barChartData?: BarChartData[]; uniformityData?: Uniformity | null;
gaugeChartData?: GaugeChartData;
uniformityDetails?: UniformityDetailItem[]; uniformityDetails?: UniformityDetailItem[];
} }
const UniformityChart = ({ const UniformityChart = ({
barChartData: initialBarChartData, uniformityData,
gaugeChartData: initialGaugeChartData,
uniformityDetails, uniformityDetails,
}: UniformityChartProps) => { }: 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[] = [ const defaultUniformityDetails: UniformityDetailItem[] = [
{ id: 1, weight: 61, range: 'Ideal' }, { id: 1, weight: 61, range: 'Ideal' },
{ id: 2, weight: 62, range: 'Ideal' }, { id: 2, weight: 62, range: 'Ideal' },
@@ -100,34 +31,63 @@ const UniformityChart = ({
const detailsToUse = uniformityDetails || defaultUniformityDetails; const detailsToUse = uniformityDetails || defaultUniformityDetails;
const barChartData = useMemo(() => { const barChartData = useMemo(() => {
const dataToProcess = initialBarChartData || defaultBarChartData; if (!uniformityData) {
return [];
if (!detailsToUse || detailsToUse.length === 0) {
return dataToProcess;
} }
return dataToProcess.map((bar) => { if (!detailsToUse || detailsToUse.length === 0) {
const rangeMatch = bar.name.match(/(\d+)-(\d+)/); return [];
if (!rangeMatch) return bar; }
const minWeight = parseInt(rangeMatch[1], 10); const weights = detailsToUse.map((d) => d.weight);
const maxWeight = parseInt(rangeMatch[2], 10); const minWeight = Math.floor(Math.min(...weights) / 5) * 5;
const maxWeight = Math.ceil(Math.max(...weights) / 5) * 5;
const hasIdealWeight = detailsToUse.some((detail) => { const rangeSize = maxWeight - minWeight < 11 ? 4 : 5;
const weight = detail.weight; const ranges: string[] = [];
return (
detail.range === 'Ideal' && weight >= minWeight && weight <= maxWeight 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 { return {
...bar, name: range,
isIdeal: hasIdealWeight, 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 ( return (
<section className='w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-4 gap-4'> <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'> <div className='w-full h-full flex items-center justify-center'>
{barChartData.length === 0 ? ( {!uniformityData || barChartData.length === 0 ? (
<UniformityBarChartSkeleton /> <UniformityBarChartSkeleton />
) : ( ) : (
<UniformityBarChart data={barChartData} /> <UniformityBarChart data={barChartData} />
)} )}
</div> </div>
</Card> </Card>
{gaugeChartData && gaugeChartData.value === 0 ? ( {!uniformityData || !gaugeChartData ? (
<Card <Card
variant='bordered' variant='bordered'
title='Weekly Performance ⓘ' title='Weekly Performance ⓘ'
@@ -158,7 +118,7 @@ const UniformityChart = ({
> >
<UniformityGaugeChartSkeleton /> <UniformityGaugeChartSkeleton />
</Card> </Card>
) : gaugeChartData ? ( ) : (
<Card <Card
variant='bordered' variant='bordered'
title='Weekly Performance ⓘ' title='Weekly Performance ⓘ'
@@ -175,7 +135,7 @@ const UniformityChart = ({
totalValue={gaugeChartData.totalValue} totalValue={gaugeChartData.totalValue}
/> />
</Card> </Card>
) : null} )}
</section> </section>
); );
}; };
@@ -13,6 +13,7 @@ import { UniformityApi } from '@/services/api/uniformity';
import { import {
DetailOptionType, DetailOptionType,
type Uniformity, type Uniformity,
type UniformityDetail,
} from '@/types/api/production/uniformity'; } from '@/types/api/production/uniformity';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { type BaseApiResponse } from '@/types/api/api-general'; import { type BaseApiResponse } from '@/types/api/api-general';
@@ -79,7 +80,7 @@ const UniformityConfirmationPreview = ({
{ {
id: 'file-uniformity', id: 'file-uniformity',
label: 'File Uniformity', label: 'File Uniformity',
value: '-', // File name tidak tersedia di GET ALL response value: '-',
}, },
{ {
id: 'status', 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 UniformityTable = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@@ -830,7 +876,7 @@ const UniformityTable = () => {
<div className='my-4 divider'></div> <div className='my-4 divider'></div>
<section> <section>
<UniformityChart /> <UniformityChartWrapper uniformitySwrKey={uniformitySwrKey} />
</section> </section>
<Card <Card
@@ -27,6 +27,7 @@ interface BarChartData {
name: string; name: string;
uv: number; uv: number;
isIdeal?: boolean; isIdeal?: boolean;
idealCount?: number;
} }
interface UniformityBarChartProps { interface UniformityBarChartProps {
@@ -35,7 +36,25 @@ interface UniformityBarChartProps {
function CustomTooltip({ payload, label, active }: CustomTooltipProps) { function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
if (active && payload && payload.length && label !== undefined) { 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); 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 ( return (
<div className='bg-[#18181B] p-2.5 shadow-sm text-white rounded-2xl rounded-bl-none'> <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> <p className='m-0 font-bold text-white/50'>Uniformity 2025</p>