mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Refactor Uniformity charts to use API data
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user