bugs fixed for phase 2
This commit is contained in:
parent
fc4535943c
commit
093eed99b2
@ -9,7 +9,8 @@ import { SelectField as BaseSelectField } from '@/components/common/FormControls
|
||||
import Table from '@/components/common/Table';
|
||||
import {
|
||||
FiberManualRecord as FiberManualRecordIcon,
|
||||
KeyboardArrowRight as KeyboardArrowRightIcon
|
||||
KeyboardArrowRight as KeyboardArrowRightIcon,
|
||||
InfoOutlined as InfoOutlinedIcon
|
||||
} from '@mui/icons-material';
|
||||
import Card from '@/components/common/Card';
|
||||
import { getManufacturingIndex, getManufacturingMonthlyOverview } from '@/services/manufacturingIndex/ManufacturingIndex';
|
||||
@ -40,10 +41,11 @@ const SelectField = (props) => (
|
||||
|
||||
const ManufacturingIndex = () => {
|
||||
// State management
|
||||
const [year, setYear] = useState('2025');
|
||||
const [year, setYear] = useState('All');
|
||||
const [searchMonth, setSearchMonth] = useState('');
|
||||
const [selectedMonth, setSelectedMonth] = useState(null);
|
||||
const [filteredMonths, setFilteredMonths] = useState([]);
|
||||
const [allMonths, setAllMonths] = useState([]); // Store all months data
|
||||
const [filteredMonths, setFilteredMonths] = useState([]); // Store filtered months
|
||||
const [activeTab, setActiveTab] = useState('manufacturing');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
@ -65,216 +67,214 @@ const ManufacturingIndex = () => {
|
||||
};
|
||||
|
||||
// Fetch data from API
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await getManufacturingIndex();
|
||||
|
||||
// Handle different response structures
|
||||
let dataArray = [];
|
||||
|
||||
// Case 1: Response is already an array
|
||||
if (Array.isArray(response)) {
|
||||
dataArray = response;
|
||||
}
|
||||
// Case 2: Response has data property which is array
|
||||
else if (response && response.data && Array.isArray(response.data)) {
|
||||
dataArray = response.data;
|
||||
}
|
||||
// Case 3: Response.data is object with nested data
|
||||
else if (response && response.data && typeof response.data === 'object') {
|
||||
// Try to extract array from common structures
|
||||
if (Array.isArray(response.data.manufacturingIndex) || Array.isArray(response.data.data)) {
|
||||
dataArray = response.data.manufacturingIndex || response.data.data || [];
|
||||
} else {
|
||||
// Convert object values to array
|
||||
dataArray = Object.values(response.data);
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await getManufacturingIndex();
|
||||
|
||||
// Handle different response structures
|
||||
let dataArray = [];
|
||||
|
||||
// Case 1: Response is already an array
|
||||
if (Array.isArray(response)) {
|
||||
dataArray = response;
|
||||
}
|
||||
}
|
||||
// Case 4: Response has nested structure
|
||||
else if (response && response.data && response.data.data && Array.isArray(response.data.data)) {
|
||||
dataArray = response.data.data;
|
||||
}
|
||||
else {
|
||||
console.error('Unexpected API response structure:', response);
|
||||
// Try to extract any array from response
|
||||
const findArray = (obj) => {
|
||||
if (Array.isArray(obj)) return obj;
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
for (const key in obj) {
|
||||
if (Array.isArray(obj[key])) {
|
||||
return obj[key];
|
||||
// Case 2: Response has data property which is array
|
||||
else if (response && response.data && Array.isArray(response.data)) {
|
||||
dataArray = response.data;
|
||||
}
|
||||
// Case 3: Response.data is object with nested data
|
||||
else if (response && response.data && typeof response.data === 'object') {
|
||||
// Try to extract array from common structures
|
||||
if (Array.isArray(response.data.manufacturingIndex) || Array.isArray(response.data.data)) {
|
||||
dataArray = response.data.manufacturingIndex || response.data.data || [];
|
||||
} else {
|
||||
// Convert object values to array
|
||||
dataArray = Object.values(response.data);
|
||||
}
|
||||
}
|
||||
// Case 4: Response has nested structure
|
||||
else if (response && response.data && response.data.data && Array.isArray(response.data.data)) {
|
||||
dataArray = response.data.data;
|
||||
}
|
||||
else {
|
||||
console.error('Unexpected API response structure:', response);
|
||||
// Try to extract any array from response
|
||||
const findArray = (obj) => {
|
||||
if (Array.isArray(obj)) return obj;
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
for (const key in obj) {
|
||||
if (Array.isArray(obj[key])) {
|
||||
return obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
dataArray = findArray(response);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
dataArray = findArray(response);
|
||||
}
|
||||
|
||||
// If no data found
|
||||
if (!dataArray || dataArray.length === 0) {
|
||||
console.warn('No data array found, using empty array');
|
||||
dataArray = [];
|
||||
}
|
||||
|
||||
// Format the data
|
||||
const formattedData = dataArray.map((item, index) => {
|
||||
// Create a date object from reference_date or other date fields
|
||||
let dateObj = new Date();
|
||||
let year = 2025;
|
||||
let monthNumber = 1;
|
||||
|
||||
if (item.reference_date) {
|
||||
dateObj = new Date(item.reference_date);
|
||||
} else if (item.date) {
|
||||
dateObj = new Date(item.date);
|
||||
} else if (item.referenceDate) {
|
||||
dateObj = new Date(item.referenceDate);
|
||||
} else if (item.year && item.month) {
|
||||
// If year and month are separate fields
|
||||
year = item.year;
|
||||
monthNumber = item.month;
|
||||
dateObj = new Date(year, monthNumber - 1, 1);
|
||||
// If no data found
|
||||
if (!dataArray || dataArray.length === 0) {
|
||||
console.warn('No data array found, using empty array');
|
||||
dataArray = [];
|
||||
}
|
||||
|
||||
const monthName = dateObj.toLocaleString('en-US', { month: 'short' });
|
||||
year = item.year || dateObj.getFullYear() || 2025;
|
||||
monthNumber = item.month || (dateObj.getMonth() + 1) || 1;
|
||||
|
||||
return {
|
||||
id: item.id || `${year}-${monthNumber}` || `item-${index}`,
|
||||
month: `${monthName} ${year}`,
|
||||
monthName: monthName,
|
||||
year: year,
|
||||
monthNumber: monthNumber,
|
||||
index: item.manufacturing_index?.toString() || item.index?.toString() || item.manufacturingIndex?.toString() || 'N/A',
|
||||
mom: item.mom_change || item.mom || item.momChange ?
|
||||
(parseFloat(item.mom_change || item.mom || item.momChange) > 0 ?
|
||||
`+${item.mom_change || item.mom || item.momChange}` :
|
||||
(item.mom_change || item.mom || item.momChange).toString()) :
|
||||
'N/A',
|
||||
yoy: item.yoy_change || item.yoy || item.yoyChange ?
|
||||
(parseFloat(item.yoy_change || item.yoy || item.yoyChange) > 0 ?
|
||||
`+${item.yoy_change || item.yoy || item.yoyChange}` :
|
||||
(item.yoy_change || item.yoy || item.yoyChange).toString()) :
|
||||
'N/A',
|
||||
generated: item.generated_on || item.generatedOn || item.generated_at ?
|
||||
new Date(item.generated_on || item.generatedOn || item.generated_at).toLocaleString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'Asia/Dubai',
|
||||
timeZoneName: 'short'
|
||||
}) : 'N/A',
|
||||
status: item.status || 'Completed',
|
||||
rawData: item
|
||||
};
|
||||
});
|
||||
// Format the data
|
||||
const formattedData = dataArray.map((item, index) => {
|
||||
// Create a date object from reference_date or other date fields
|
||||
let dateObj = new Date();
|
||||
let year = 2025;
|
||||
let monthNumber = 1;
|
||||
|
||||
if (item.reference_date) {
|
||||
dateObj = new Date(item.reference_date);
|
||||
} else if (item.date) {
|
||||
dateObj = new Date(item.date);
|
||||
} else if (item.referenceDate) {
|
||||
dateObj = new Date(item.referenceDate);
|
||||
} else if (item.year && item.month) {
|
||||
// If year and month are separate fields
|
||||
year = item.year;
|
||||
monthNumber = item.month;
|
||||
dateObj = new Date(year, monthNumber - 1, 1);
|
||||
}
|
||||
|
||||
const monthName = dateObj.toLocaleString('en-US', { month: 'short' });
|
||||
year = item.year || dateObj.getFullYear() || 2025;
|
||||
monthNumber = item.month || (dateObj.getMonth() + 1) || 1;
|
||||
|
||||
return {
|
||||
id: item.id || `${year}-${monthNumber}` || `item-${index}`,
|
||||
month: `${monthName} ${year}`,
|
||||
monthName: monthName,
|
||||
year: year,
|
||||
monthNumber: monthNumber,
|
||||
index: item.manufacturing_index?.toString() || item.index?.toString() || item.manufacturingIndex?.toString() || 'N/A',
|
||||
mom: item.mom_change || item.mom || item.momChange ?
|
||||
(parseFloat(item.mom_change || item.mom || item.momChange) > 0 ?
|
||||
`+${item.mom_change || item.mom || item.momChange}` :
|
||||
(item.mom_change || item.mom || item.momChange).toString()) :
|
||||
'N/A',
|
||||
yoy: item.yoy_change || item.yoy || item.yoyChange ?
|
||||
(parseFloat(item.yoy_change || item.yoy || item.yoyChange) > 0 ?
|
||||
`+${item.yoy_change || item.yoy || item.yoyChange}` :
|
||||
(item.yoy_change || item.yoy || item.yoyChange).toString()) :
|
||||
'N/A',
|
||||
generated: item.generated_on || item.generatedOn || item.generated_at ?
|
||||
new Date(item.generated_on || item.generatedOn || item.generated_at).toLocaleString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'Asia/Dubai',
|
||||
timeZoneName: 'short'
|
||||
}) : 'N/A',
|
||||
status: item.status || 'Completed',
|
||||
rawData: item
|
||||
};
|
||||
});
|
||||
|
||||
setFilteredMonths(formattedData);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
totalItems: formattedData.length
|
||||
}));
|
||||
|
||||
if (formattedData.length > 0) {
|
||||
setSelectedMonth(formattedData[0]);
|
||||
setAllMonths(formattedData);
|
||||
applyFilters(year, searchMonth, formattedData);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
totalItems: formattedData.length
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Error fetching manufacturing index:', err);
|
||||
console.error('Error details:', {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
response: err.response
|
||||
});
|
||||
setError('Failed to load manufacturing data. Please try again later.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching manufacturing index:', err);
|
||||
console.error('Error details:', {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
response: err.response
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
// ARROW CLICK
|
||||
const handleMonthSelect = async (row) => {
|
||||
try {
|
||||
|
||||
const yearNumber = row.year;
|
||||
const monthNumber = row.monthNumber;
|
||||
|
||||
setSelectedMonth(row);
|
||||
setIsPopupOpen(true);
|
||||
setApiLoading(true);
|
||||
|
||||
// API CALL
|
||||
const apiResponse = await getManufacturingMonthlyOverview(yearNumber, monthNumber);
|
||||
|
||||
// Extract data from the response structure
|
||||
// Note: Your API returns {status, message, manufacturing_total, isic_2digit, isic_3digit, isic_4digit}
|
||||
const responseData = apiResponse.data || apiResponse;
|
||||
|
||||
// Transform data based on YOUR API structure
|
||||
const transformedData = {
|
||||
manufacturing: responseData?.manufacturing_total || {},
|
||||
isic2: Array.isArray(responseData?.isic_2digit) ? responseData.isic_2digit : [],
|
||||
isic3: Array.isArray(responseData?.isic_3digit) ? responseData.isic_3digit : [],
|
||||
isic4: Array.isArray(responseData?.isic_4digit) ? responseData.isic_4digit : []
|
||||
};
|
||||
|
||||
// 4 popups-க்கும் data set பண்ணு
|
||||
setMonthlyOverviewData(transformedData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in handleMonthSelect:', error);
|
||||
setError(`API Error: ${error.message || 'Failed to load detailed data'}`);
|
||||
|
||||
// Fallback: empty data set
|
||||
setMonthlyOverviewData({
|
||||
manufacturing: {},
|
||||
isic2: [],
|
||||
isic3: [],
|
||||
isic4: []
|
||||
});
|
||||
setError('Failed to load manufacturing data. Please try again later.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setApiLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
// ARROW CLICK செய்தால் இந்த function வேலை செய்யும்
|
||||
const handleMonthSelect = async (row) => {
|
||||
try {
|
||||
|
||||
const yearNumber = row.year;
|
||||
const monthNumber = row.monthNumber;
|
||||
|
||||
setSelectedMonth(row);
|
||||
setActiveTab('manufacturing');
|
||||
setIsPopupOpen(true);
|
||||
setApiLoading(true);
|
||||
|
||||
// API CALL
|
||||
const apiResponse = await getManufacturingMonthlyOverview(yearNumber, monthNumber);
|
||||
|
||||
// Extract data from the response structure
|
||||
// Note: Your API returns {status, message, manufacturing_total, isic_2digit, isic_3digit, isic_4digit}
|
||||
const responseData = apiResponse.data || apiResponse;
|
||||
|
||||
// Transform data based on YOUR API structure
|
||||
const transformedData = {
|
||||
manufacturing: responseData?.manufacturing_total || {},
|
||||
isic2: Array.isArray(responseData?.isic_2digit) ? responseData.isic_2digit : [],
|
||||
isic3: Array.isArray(responseData?.isic_3digit) ? responseData.isic_3digit : [],
|
||||
isic4: Array.isArray(responseData?.isic_4digit) ? responseData.isic_4digit : []
|
||||
};
|
||||
|
||||
// 4 popups-க்கும் data set பண்ணு
|
||||
setMonthlyOverviewData(transformedData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in handleMonthSelect:', error);
|
||||
setError(`API Error: ${error.message || 'Failed to load detailed data'}`);
|
||||
|
||||
// Fallback: empty data set
|
||||
setMonthlyOverviewData({
|
||||
manufacturing: {},
|
||||
isic2: [],
|
||||
isic3: [],
|
||||
isic4: []
|
||||
});
|
||||
} finally {
|
||||
setApiLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClosePopup = () => {
|
||||
setIsPopupOpen(false);
|
||||
setMonthlyOverviewData(null);
|
||||
};
|
||||
|
||||
const handleYearChange = (e) => {
|
||||
const selectedYear = e.target.value;
|
||||
setYear(selectedYear);
|
||||
filterMonths(selectedYear, searchMonth);
|
||||
};
|
||||
|
||||
const handleSearchMonth = (e) => {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
setSearchMonth(searchTerm);
|
||||
filterMonths(year, searchTerm);
|
||||
};
|
||||
|
||||
const filterMonths = (selectedYear, searchTerm) => {
|
||||
const filtered = filteredMonths.filter(month => {
|
||||
const matchesYear = month.month.endsWith(selectedYear);
|
||||
const matchesSearch = month.month.toLowerCase().includes(searchTerm);
|
||||
return matchesYear && (searchTerm === '' || matchesSearch);
|
||||
// Apply both year and search filters
|
||||
const applyFilters = (selectedYear, searchTerm, monthsData = allMonths) => {
|
||||
const filtered = monthsData.filter(month => {
|
||||
const matchesYear = selectedYear === 'All' || month.year.toString() === selectedYear;
|
||||
const matchesSearch = searchTerm === '' || month.month.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
return matchesYear && matchesSearch;
|
||||
});
|
||||
|
||||
setFilteredMonths(filtered);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
currentPage: 1,
|
||||
totalItems: filtered.length
|
||||
}));
|
||||
return filtered;
|
||||
};
|
||||
|
||||
const handleYearChange = (e) => {
|
||||
const selectedYear = e.target.value;
|
||||
setYear(selectedYear);
|
||||
applyFilters(selectedYear, searchMonth);
|
||||
};
|
||||
|
||||
const handleSearchMonth = (e) => {
|
||||
const searchTerm = e.target.value;
|
||||
setSearchMonth(searchTerm);
|
||||
applyFilters(year, searchTerm);
|
||||
};
|
||||
|
||||
const handleExportCSV = () => {
|
||||
@ -302,7 +302,14 @@ const handleMonthSelect = async (row) => {
|
||||
};
|
||||
|
||||
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
|
||||
const years = ['2025', '2024', '2023'];
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = [
|
||||
'All',
|
||||
...Array.from(
|
||||
{ length: currentYear - 2021 },
|
||||
(_, i) => (currentYear - i).toString()
|
||||
)
|
||||
];
|
||||
const selectedMonthData = selectedMonth || {};
|
||||
|
||||
// Show loading state
|
||||
@ -351,7 +358,8 @@ const handleMonthSelect = async (row) => {
|
||||
className="relative"
|
||||
>
|
||||
<DialogTitle className="bg-[#F9F5E8] text-[#92722A] flex justify-between items-center pr-2">
|
||||
<span>Manufacturing Details - {selectedMonth?.month}</span>
|
||||
<span>Monthly ISIC breakdown
|
||||
- {selectedMonth?.month}</span>
|
||||
<IconButton
|
||||
onClick={handleClosePopup}
|
||||
size="small"
|
||||
@ -376,6 +384,9 @@ const handleMonthSelect = async (row) => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-0 w-[1000px] max-w-[95vw] min-w-[800px]">
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
Start from the manufacturing total, then drill down into ISIC 2-digit and 3 & 4-digit groups for the selected month.
|
||||
</p>
|
||||
{/* Tabs Header */}
|
||||
<div className="sticky top-0 z-10 bg-white pt-2">
|
||||
<div className="flex justify-between items-center border-b border-gray-200 pb-2">
|
||||
@ -452,9 +463,15 @@ const handleMonthSelect = async (row) => {
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<div className="mt-5 flex items-center justify-end text-sm text-green-600 mb-1">
|
||||
<FiberManualRecordIcon className="text-xs mr-1" />
|
||||
Next scheduled run: 11 Nov 2025, 02:00 GST
|
||||
<div className="mt-5 flex flex-col items-end gap-2">
|
||||
<div className="flex items-center text-sm text-green-600">
|
||||
<FiberManualRecordIcon className="text-xs mr-1" />
|
||||
Next scheduled run: 11 Nov 2025, 02:00 GST
|
||||
</div>
|
||||
<div className="flex text-sm text-gray-600">
|
||||
<InfoOutlinedIcon className="text-gray-400 mr-1 mt-0.5 flex-shrink-0" style={{ fontSize: '1rem' }} />
|
||||
<span className="whitespace-nowrap">Formula: Manufacturing index<sub>t</sub> = Σ (sector weight<sub>i</sub> × group index<sub>i,t</sub>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -476,6 +493,14 @@ const handleMonthSelect = async (row) => {
|
||||
<p className="text-sm text-gray-600">
|
||||
Filter by year and quickly jump to a month to see its ISIC breakdown.
|
||||
</p>
|
||||
<div className="flex items-center gap-4 mt-2">
|
||||
<div className="text-sm text-gray-600 bg-gray-100 px-3 py-1 rounded-full">
|
||||
Base year: 2022 = 100
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 bg-gray-100 px-3 py-1 rounded-full">
|
||||
Coverage: Manufacturing (ISIC C)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 w-full sm:w-auto">
|
||||
<div className="flex-1 sm:flex-none w-40">
|
||||
|
||||
@ -97,6 +97,23 @@ const ManufacturingTotal = ({ selectedMonthData, setActiveTab, monthlyOverviewDa
|
||||
the same month last year.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 p-4 bg-gray-50 rounded-md text-sm text-gray-700 max-w-3xl">
|
||||
<p className="font-medium mb-2">Laspeyres reminder</p>
|
||||
<p className="font mb-2">Manufacturing index<sub>t</sub> = Σ (weight<sub>j</sub> × group_index<sub>j,t</sub>)</p>
|
||||
<p className="text-xs text-gray-600 mb-4">
|
||||
Weights are fixed at base year (2022) and reflect each group's share in total manufacturing value added.
|
||||
</p>
|
||||
|
||||
<div className="border-t border-gray-200 pt-3 mt-3">
|
||||
<p className="font-medium mb-2">Operational notes</p>
|
||||
<ul className="text-xs text-gray-600 space-y-1.5 list-disc pl-5">
|
||||
<li>Manufacturing indices are auto-generated on the 11th using the latest validated item-level data for the previous reference month.</li>
|
||||
<li>If an automated run fails, the status for that month will show as Error; admins can re-trigger the calculation from the technical tools page.</li>
|
||||
<li>All drill-down views (ISIC 2-digit and ISIC 3 & 4-digit) use the same Laspeyres weights and base year as the headline manufacturing index.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-4 border-gray-200 flex justify-end">
|
||||
<button
|
||||
onClick={() => setActiveTab('isic2')}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user