mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Use chart_data for Uniformity and add week nav
This commit is contained in:
@@ -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
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user