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