mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
Merge branch 'dev/restu' into 'development'
[FEAT/FE] Add Uniformity Chart Data (Ideal and Outside Range) See merge request mbugroup/lti-web-client!147
This commit is contained in:
Generated
+20
-4
@@ -4506,6 +4506,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -4516,6 +4517,7 @@
|
||||
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -4597,6 +4599,7 @@
|
||||
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.46.2",
|
||||
"@typescript-eslint/types": "8.46.2",
|
||||
@@ -5120,6 +5123,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5825,7 +5829,8 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
@@ -6201,7 +6206,8 @@
|
||||
"version": "8.6.0",
|
||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
||||
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/embla-carousel-react": {
|
||||
"version": "8.6.0",
|
||||
@@ -6462,6 +6468,7 @@
|
||||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -6635,6 +6642,7 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@@ -8152,6 +8160,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz",
|
||||
"integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"fast-png": "^6.2.0",
|
||||
@@ -9371,6 +9380,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -9401,6 +9411,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@@ -9468,7 +9479,8 @@
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/react-number-format": {
|
||||
"version": "5.4.4",
|
||||
@@ -9485,6 +9497,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
@@ -9653,7 +9666,8 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
@@ -10519,6 +10533,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -10686,6 +10701,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -189,12 +189,45 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
return;
|
||||
}
|
||||
const documents: File[] = [];
|
||||
const documentNameToIndex = new Map<string, number>();
|
||||
let sequentialDocumentIndex = 0;
|
||||
|
||||
const deliveriesPayload = values.deliveries.map((d) => {
|
||||
let documentIndex = 0;
|
||||
let documentIndex = -1;
|
||||
|
||||
if (d.document && d.document instanceof File) {
|
||||
documents.push(d.document);
|
||||
documentIndex = documents.length - 1;
|
||||
const fileName = d.document.name;
|
||||
|
||||
if (documentNameToIndex.has(fileName)) {
|
||||
documentIndex = documentNameToIndex.get(fileName)!;
|
||||
} else {
|
||||
documents.push(d.document);
|
||||
documentIndex = sequentialDocumentIndex;
|
||||
documentNameToIndex.set(fileName, documentIndex);
|
||||
sequentialDocumentIndex++;
|
||||
}
|
||||
} else if (d.document_path) {
|
||||
const pathFileName =
|
||||
d.document_path.split('/').pop() || d.document_path;
|
||||
|
||||
if (documentNameToIndex.has(pathFileName)) {
|
||||
documentIndex = documentNameToIndex.get(pathFileName)!;
|
||||
} else {
|
||||
documentIndex = sequentialDocumentIndex;
|
||||
documentNameToIndex.set(pathFileName, documentIndex);
|
||||
sequentialDocumentIndex++;
|
||||
}
|
||||
} else if (d.document && !(d.document instanceof File)) {
|
||||
const existingDocFileName =
|
||||
d.document.path.split('/').pop() || d.document.path;
|
||||
|
||||
if (documentNameToIndex.has(existingDocFileName)) {
|
||||
documentIndex = documentNameToIndex.get(existingDocFileName)!;
|
||||
} else {
|
||||
documentIndex = sequentialDocumentIndex;
|
||||
documentNameToIndex.set(existingDocFileName, documentIndex);
|
||||
sequentialDocumentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -202,7 +235,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
delivery_cost_per_item:
|
||||
parseInt((d.delivery_cost_per_item || '').toString()) || 0,
|
||||
document_index: documentIndex,
|
||||
document_path: d.document_path,
|
||||
driver_name: d.driver_name,
|
||||
vehicle_plate: d.vehicle_plate,
|
||||
supplier_id: d.supplier_id,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo, useState, useEffect } 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';
|
||||
@@ -22,13 +22,27 @@ const UniformityChart = ({
|
||||
return uniformityData.chart_data;
|
||||
}, [uniformityData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (uniformityData?.chart_data?.gauge_chart?.week_info) {
|
||||
const { current_week_index } =
|
||||
uniformityData.chart_data.gauge_chart.week_info;
|
||||
setCurrentWeekIndex(current_week_index);
|
||||
}
|
||||
}, [uniformityData]);
|
||||
|
||||
const barChartData = useMemo(() => {
|
||||
if (!chartData?.bar_chart) {
|
||||
if (!chartData?.bar_chart || !chartData?.gauge_chart) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { bar_chart } = chartData;
|
||||
const currentWeekStr = String(bar_chart.current_week);
|
||||
const { bar_chart, gauge_chart } = chartData;
|
||||
const currentWeekData = gauge_chart.available_weeks[currentWeekIndex];
|
||||
|
||||
if (!currentWeekData || !currentWeekData.has_data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const currentWeekStr = String(currentWeekData.week);
|
||||
const weekData = bar_chart.all_weeks[currentWeekStr];
|
||||
|
||||
if (!weekData || !weekData.has_data) {
|
||||
@@ -39,11 +53,10 @@ const UniformityChart = ({
|
||||
name: range.range,
|
||||
uv: range.bird_count,
|
||||
isIdeal: range.is_ideal_range,
|
||||
idealCount: range.is_ideal_range
|
||||
? weekData.ideal_range.total_ideal_birds
|
||||
: undefined,
|
||||
idealRange: range.ideal_range,
|
||||
outsideRange: range.outside_range,
|
||||
}));
|
||||
}, [chartData]);
|
||||
}, [chartData, currentWeekIndex]);
|
||||
|
||||
const gaugeChartData = useMemo(() => {
|
||||
if (!chartData?.gauge_chart || !uniformityData) return undefined;
|
||||
@@ -55,28 +68,33 @@ const UniformityChart = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const hasPrevWeek = currentWeekIndex > 0;
|
||||
const hasNextWeek =
|
||||
currentWeekIndex < gauge_chart.available_weeks.length - 1;
|
||||
|
||||
return {
|
||||
value: currentWeekData.uniformity_percentage,
|
||||
label: 'Uniformity',
|
||||
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,
|
||||
hasPrevWeek,
|
||||
hasNextWeek,
|
||||
};
|
||||
}, [chartData, currentWeekIndex, uniformityData]);
|
||||
|
||||
const handleWeekChange = (direction: 'prev' | 'next') => {
|
||||
if (!chartData?.gauge_chart) return;
|
||||
|
||||
const { available_weeks, week_info } = chartData.gauge_chart;
|
||||
const { available_weeks } = 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)
|
||||
);
|
||||
if (direction === 'prev' && currentWeekIndex > 0) {
|
||||
setCurrentWeekIndex((prev) => prev - 1);
|
||||
} else if (
|
||||
direction === 'next' &&
|
||||
currentWeekIndex < available_weeks.length - 1
|
||||
) {
|
||||
setCurrentWeekIndex((prev) => prev + 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -230,6 +230,7 @@ const UniformityTable = () => {
|
||||
const [filterStartDate, setFilterStartDate] = useState('');
|
||||
const [filterEndDate, setFilterEndDate] = useState('');
|
||||
const [projectFlockSearchValue, setProjectFlockSearchValue] = useState('');
|
||||
const [filterErrors, setFilterErrors] = useState<Record<string, string>>({});
|
||||
|
||||
const {
|
||||
setInputValue: setFilterLocationInputValue,
|
||||
@@ -423,9 +424,38 @@ const UniformityTable = () => {
|
||||
}, []);
|
||||
|
||||
const handleApplyFilters = useCallback(() => {
|
||||
setIsSubmitted(true);
|
||||
filterModal.closeModal();
|
||||
}, [filterModal]);
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
if (!filterStartDate) {
|
||||
errors.start_date = 'Tanggal mulai wajib diisi';
|
||||
}
|
||||
if (!filterEndDate) {
|
||||
errors.end_date = 'Tanggal akhir wajib diisi';
|
||||
}
|
||||
if (!filterLocation) {
|
||||
errors.location = 'Lokasi wajib dipilih';
|
||||
}
|
||||
if (!filterProjectFlock) {
|
||||
errors.project_flock = 'Project Flock wajib dipilih';
|
||||
}
|
||||
if (!filterKandang) {
|
||||
errors.kandang = 'Kandang wajib dipilih';
|
||||
}
|
||||
|
||||
setFilterErrors(errors);
|
||||
|
||||
if (Object.keys(errors).length === 0) {
|
||||
setIsSubmitted(true);
|
||||
filterModal.closeModal();
|
||||
}
|
||||
}, [
|
||||
filterModal,
|
||||
filterStartDate,
|
||||
filterEndDate,
|
||||
filterLocation,
|
||||
filterProjectFlock,
|
||||
filterKandang,
|
||||
]);
|
||||
|
||||
const selectedRowIds = useMemo(() => {
|
||||
return Object.keys(rowSelection)
|
||||
@@ -614,7 +644,7 @@ const UniformityTable = () => {
|
||||
if (filterEndDate) {
|
||||
queryParams.append('end_date', filterEndDate);
|
||||
}
|
||||
queryParams.append('limit', '10000');
|
||||
queryParams.append('limit', '100');
|
||||
queryParams.append('page', '1');
|
||||
|
||||
const queryString = queryParams.toString();
|
||||
@@ -1124,58 +1154,105 @@ const UniformityTable = () => {
|
||||
</div>
|
||||
<div className='space-y-4 px-4'>
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 sm:gap-4'>
|
||||
<DateInput
|
||||
label='Tanggal'
|
||||
name='start_date'
|
||||
value={filterStartDate}
|
||||
onChange={(e) => setFilterStartDate(e.target.value)}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
<div>
|
||||
<DateInput
|
||||
label='Tanggal'
|
||||
name='start_date'
|
||||
value={filterStartDate}
|
||||
onChange={(e) => {
|
||||
setFilterStartDate(e.target.value);
|
||||
setFilterErrors((prev) => ({ ...prev, start_date: '' }));
|
||||
}}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.start_date && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.start_date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DateInput
|
||||
label=' '
|
||||
name='end_date'
|
||||
value={filterEndDate}
|
||||
onChange={(e) => setFilterEndDate(e.target.value)}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
<div>
|
||||
<DateInput
|
||||
label=' '
|
||||
name='end_date'
|
||||
value={filterEndDate}
|
||||
onChange={(e) => {
|
||||
setFilterEndDate(e.target.value);
|
||||
setFilterErrors((prev) => ({ ...prev, end_date: '' }));
|
||||
}}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.end_date && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.end_date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SelectInput
|
||||
label='Lokasi'
|
||||
placeholder='Pilih Lokasi...'
|
||||
value={filterLocation}
|
||||
onChange={handleFilterLocationChange}
|
||||
options={filterLocationOptions}
|
||||
onInputChange={setFilterLocationInputValue}
|
||||
isLoading={isLoadingFilterLocations}
|
||||
isClearable
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
<div>
|
||||
<SelectInput
|
||||
label='Lokasi'
|
||||
placeholder='Pilih Lokasi...'
|
||||
value={filterLocation}
|
||||
onChange={(value) => {
|
||||
handleFilterLocationChange(value);
|
||||
setFilterErrors((prev) => ({ ...prev, location: '' }));
|
||||
}}
|
||||
options={filterLocationOptions}
|
||||
onInputChange={setFilterLocationInputValue}
|
||||
isLoading={isLoadingFilterLocations}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.location && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.location}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SelectInput
|
||||
label='Project Flock'
|
||||
placeholder='Pilih Project Flock...'
|
||||
value={filterProjectFlock}
|
||||
onChange={handleFilterProjectFlockChange}
|
||||
options={filterProjectFlockOptions}
|
||||
onInputChange={setProjectFlockSearchValue}
|
||||
isLoading={isLoadingFilterProjectFlocks}
|
||||
isDisabled={!filterLocation}
|
||||
isClearable
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
<div>
|
||||
<SelectInput
|
||||
label='Project Flock'
|
||||
placeholder='Pilih Project Flock...'
|
||||
value={filterProjectFlock}
|
||||
onChange={(value) => {
|
||||
handleFilterProjectFlockChange(value);
|
||||
setFilterErrors((prev) => ({ ...prev, project_flock: '' }));
|
||||
}}
|
||||
options={filterProjectFlockOptions}
|
||||
onInputChange={setProjectFlockSearchValue}
|
||||
isLoading={isLoadingFilterProjectFlocks}
|
||||
isDisabled={!filterLocation}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.project_flock && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.project_flock}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SelectInput
|
||||
label='Kandang'
|
||||
placeholder='Pilih Kandang...'
|
||||
value={filterKandang}
|
||||
onChange={handleFilterKandangChange}
|
||||
options={filterKandangOptions}
|
||||
isDisabled={!filterProjectFlock}
|
||||
isClearable
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
<div>
|
||||
<SelectInput
|
||||
label='Kandang'
|
||||
placeholder='Pilih Kandang...'
|
||||
value={filterKandang}
|
||||
onChange={(value) => {
|
||||
handleFilterKandangChange(value);
|
||||
setFilterErrors((prev) => ({ ...prev, kandang: '' }));
|
||||
}}
|
||||
options={filterKandangOptions}
|
||||
isDisabled={!filterProjectFlock}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
{filterErrors.kandang && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.kandang}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
|
||||
@@ -27,7 +27,8 @@ interface BarChartData {
|
||||
name: string;
|
||||
uv: number;
|
||||
isIdeal?: boolean;
|
||||
idealCount?: number;
|
||||
idealRange?: string;
|
||||
outsideRange?: string;
|
||||
}
|
||||
|
||||
interface UniformityBarChartProps {
|
||||
@@ -40,30 +41,117 @@ function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
|
||||
const chartData = data.payload as BarChartData;
|
||||
const labelStr = String(label);
|
||||
|
||||
if (chartData.isIdeal && chartData.idealCount !== undefined) {
|
||||
// If the range has both ideal and outside ranges (like 340-344)
|
||||
if (chartData.idealRange && chartData.outsideRange) {
|
||||
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 flex-col gap-2 mt-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='w-5 h-5 bg-[#0069E0] rounded-md'></div>
|
||||
<span className='text-sm'>Ideal</span>
|
||||
</div>
|
||||
<span className='text-sm font-medium'>
|
||||
{chartData.idealRange}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='w-5 h-5 bg-[#EF4444] rounded-md'></div>
|
||||
<span className='text-sm'>Outside</span>
|
||||
</div>
|
||||
<span className='text-sm font-medium'>
|
||||
{chartData.outsideRange}
|
||||
</span>
|
||||
</div>
|
||||
<div className='border-t border-white/20 pt-2 mt-1'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-white/70 text-sm'>Total Birds:</span>
|
||||
<span className='font-semibold'>{payload[0].value}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-center text-xs text-white/50'>{labelStr}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If the range has only ideal range
|
||||
if (chartData.idealRange) {
|
||||
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
|
||||
<span className='text-sm'>Ideal</span>
|
||||
</div>
|
||||
<span>{labelStr}</span>
|
||||
<span className='text-sm font-medium'>{chartData.idealRange}</span>
|
||||
</div>
|
||||
<div className='border-t border-white/20 pt-2 mt-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-white/70 text-sm'>Birds:</span>
|
||||
<span className='font-semibold'>{payload[0].value}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-center text-xs text-white/50 mt-1'>
|
||||
{labelStr}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If the range has only outside range
|
||||
if (chartData.outsideRange) {
|
||||
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-[#EF4444] rounded-md'></div>
|
||||
<span className='text-sm'>Outside</span>
|
||||
</div>
|
||||
<span className='text-sm font-medium'>
|
||||
{chartData.outsideRange}
|
||||
</span>
|
||||
</div>
|
||||
<div className='border-t border-white/20 pt-2 mt-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-white/70 text-sm'>Birds:</span>
|
||||
<span className='font-semibold'>{payload[0].value}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-center text-xs text-white/50 mt-1'>
|
||||
{labelStr}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback for backward compatibility
|
||||
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>
|
||||
{payload[0].value} of Birds
|
||||
<div
|
||||
className='w-5 h-5 rounded-md'
|
||||
style={{
|
||||
backgroundColor: chartData.isIdeal ? '#0069E0' : '#EF4444',
|
||||
}}
|
||||
></div>
|
||||
<span className='text-sm'>
|
||||
{chartData.isIdeal ? 'Ideal' : 'Outside'}
|
||||
</span>
|
||||
</div>
|
||||
<span className='text-sm font-medium'>{labelStr}</span>
|
||||
</div>
|
||||
<div className='border-t border-white/20 pt-2 mt-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-white/70 text-sm'>Birds:</span>
|
||||
<span className='font-semibold'>{payload[0].value}</span>
|
||||
</div>
|
||||
<span>{labelStr}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
+2
@@ -8,6 +8,8 @@ export type WeightDistributionRange = {
|
||||
max_weight: number;
|
||||
bird_count: number;
|
||||
is_ideal_range: boolean;
|
||||
ideal_range?: string;
|
||||
outside_range?: string;
|
||||
};
|
||||
|
||||
export type IdealRange = {
|
||||
|
||||
Reference in New Issue
Block a user