refactor(FE): Use chart_data for Uniformity and add week nav

This commit is contained in:
rstubryan
2026-01-08 10:17:31 +07:00
parent 662dec38bc
commit b3c4a438ad
3 changed files with 88 additions and 102 deletions
@@ -1,93 +1,86 @@
import React, { useMemo } from 'react';
import React, { useMemo, useState } from 'react';
import Card from '@/components/Card';
import UniformityBarChart from '@/components/pages/production/uniformity/chart/UniformityBarChart';
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,
Uniformity,
} from '@/types/api/production/uniformity';
import { Uniformity, type ChartData } from '@/types/api/production/uniformity';
interface UniformityChartProps {
uniformityData?: Uniformity | null;
uniformityDetails?: UniformityDetailItem[];
isFiltered?: boolean;
}
const UniformityChart = ({
uniformityData,
uniformityDetails,
isFiltered = false,
}: UniformityChartProps) => {
const defaultUniformityDetails: UniformityDetailItem[] = [
{ id: 1, weight: 61, range: 'Ideal' },
{ id: 2, weight: 62, range: 'Ideal' },
{ id: 3, weight: 63, range: 'Ideal' },
{ id: 4, weight: 64, range: 'Ideal' },
{ id: 5, weight: 65, range: 'Ideal' },
{ id: 6, weight: 66, range: 'Ideal' },
{ id: 7, weight: 67, range: 'Ideal' },
];
const [currentWeekIndex, setCurrentWeekIndex] = useState(0);
const detailsToUse = uniformityDetails || defaultUniformityDetails;
const chartData = useMemo((): ChartData | undefined => {
if (!uniformityData?.chart_data) return undefined;
return uniformityData.chart_data;
}, [uniformityData]);
const barChartData = useMemo(() => {
if (!uniformityData) {
if (!chartData?.bar_chart) {
return [];
}
if (!detailsToUse || detailsToUse.length === 0) {
const { bar_chart } = chartData;
const currentWeekStr = String(bar_chart.current_week);
const weekData = bar_chart.all_weeks[currentWeekStr];
if (!weekData || !weekData.has_data) {
return [];
}
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 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 {
name: range,
uv: birdsInRange,
isIdeal: hasIdeal,
idealCount: hasIdeal ? totalIdealCount : undefined,
};
});
}, [uniformityData, detailsToUse]);
return weekData.weight_distribution.map((range) => ({
name: range.range,
uv: range.bird_count,
isIdeal: range.is_ideal_range,
idealCount: range.is_ideal_range
? weekData.ideal_range.total_ideal_birds
: undefined,
}));
}, [chartData]);
const gaugeChartData = useMemo(() => {
if (!uniformityData) return undefined;
if (!chartData?.gauge_chart || !uniformityData) return undefined;
const { gauge_chart } = chartData;
const currentWeekData = gauge_chart.available_weeks[currentWeekIndex];
if (!currentWeekData || !currentWeekData.has_data) {
return undefined;
}
return {
value: uniformityData.uniformity,
value: currentWeekData.uniformity_percentage,
label: 'Uniformity',
week: `Week ${uniformityData.week}`,
currentValue: uniformityData.uniform_qty,
totalValue: uniformityData.chick_qty_of_weight,
week: `Week ${currentWeekData.week}`,
currentValue: currentWeekData.ideal_count,
totalValue: currentWeekData.total_count,
hasPrevWeek: gauge_chart.week_info.has_prev_week,
hasNextWeek: gauge_chart.week_info.has_next_week,
};
}, [uniformityData]);
}, [chartData, currentWeekIndex, uniformityData]);
const handleWeekChange = (direction: 'prev' | 'next') => {
if (!chartData?.gauge_chart) return;
const { available_weeks, week_info } = chartData.gauge_chart;
if (direction === 'prev' && week_info.has_prev_week) {
setCurrentWeekIndex((prev) => Math.max(0, prev - 1));
} else if (direction === 'next' && week_info.has_next_week) {
setCurrentWeekIndex((prev) =>
Math.min(available_weeks.length - 1, prev + 1)
);
}
};
const shouldShowEmptyState = !isFiltered;
return (
<section className='w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-4 gap-4'>
@@ -100,14 +93,16 @@ const UniformityChart = ({
}}
>
<div className='w-full h-full flex items-center justify-center'>
{!uniformityData || barChartData.length === 0 ? (
{shouldShowEmptyState ||
!uniformityData ||
barChartData.length === 0 ? (
<UniformityBarChartSkeleton />
) : (
<UniformityBarChart data={barChartData} />
)}
</div>
</Card>
{!uniformityData || !gaugeChartData ? (
{shouldShowEmptyState || !uniformityData || !gaugeChartData ? (
<Card
variant='bordered'
title='Weekly Performance ⓘ'
@@ -133,6 +128,9 @@ const UniformityChart = ({
week={gaugeChartData.week}
currentValue={gaugeChartData.currentValue}
totalValue={gaugeChartData.totalValue}
onWeekChange={handleWeekChange}
hasPrevWeek={gaugeChartData.hasPrevWeek}
hasNextWeek={gaugeChartData.hasNextWeek}
/>
</Card>
)}
@@ -151,8 +151,10 @@ const UniformityConfirmationPreview = ({
const UniformityChartWrapper = ({
uniformitySwrKey,
isFiltered,
}: {
uniformitySwrKey: string;
isFiltered: boolean;
}) => {
const { data: uniformities } = useSWR(
uniformitySwrKey,
@@ -166,31 +168,8 @@ const UniformityChartWrapper = ({
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}
/>
<UniformityChart uniformityData={uniformityData} isFiltered={isFiltered} />
);
};
@@ -897,7 +876,10 @@ const UniformityTable = () => {
<div className='my-4 divider'></div>
<section>
<UniformityChartWrapper uniformitySwrKey={uniformitySwrKey} />
<UniformityChartWrapper
uniformitySwrKey={uniformitySwrKey}
isFiltered={isSubmitted}
/>
</section>
<Card
+19 -13
View File
@@ -2,26 +2,32 @@ import { BaseMetadata } from '@/types/api/api-general';
import { BaseApproval } from '@/types/api/approval/approval';
// ==================== CHART DATA TYPES ====================
export type WeightDistributionData = {
weight: number;
count: number;
export type WeightDistributionRange = {
range: string;
min_weight: number;
max_weight: number;
bird_count: number;
is_ideal_range: boolean;
};
export type IdealRange = {
min_weight: number;
max_weight: number;
total_ideal_birds: number;
};
export type StatisticsData = {
mean: number;
standardDeviation: number;
min: number;
max: number;
min_weight: number;
max_weight: number;
average_weight: number;
total_birds_measured: number;
};
export type WeekBarChartData = {
has_data: boolean;
weight_distribution: WeightDistributionData[];
ideal_range: {
min: number;
max: number;
} | null;
statistics: StatisticsData | null;
weight_distribution: WeightDistributionRange[];
ideal_range: IdealRange;
statistics: StatisticsData;
};
export type BarChart = {