diff --git a/ipi-survey-platform/src/pages/ManufacturingIndex/ManufacturingIndex.jsx b/ipi-survey-platform/src/pages/ManufacturingIndex/ManufacturingIndex.jsx index 4dd925f..94a07a3 100644 --- a/ipi-survey-platform/src/pages/ManufacturingIndex/ManufacturingIndex.jsx +++ b/ipi-survey-platform/src/pages/ManufacturingIndex/ManufacturingIndex.jsx @@ -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" > - Manufacturing Details - {selectedMonth?.month} + Monthly ISIC breakdown + - {selectedMonth?.month} { ) : (
+

+ Start from the manufacturing total, then drill down into ISIC 2-digit and 3 & 4-digit groups for the selected month. +

{/* Tabs Header */}
@@ -452,9 +463,15 @@ const handleMonthSelect = async (row) => {
-
- - Next scheduled run: 11 Nov 2025, 02:00 GST +
+
+ + Next scheduled run: 11 Nov 2025, 02:00 GST +
+
+ + Formula: Manufacturing indext = Σ (sector weighti × group indexi,t) +
@@ -476,6 +493,14 @@ const handleMonthSelect = async (row) => {

Filter by year and quickly jump to a month to see its ISIC breakdown.

+
+
+ Base year: 2022 = 100 +
+
+ Coverage: Manufacturing (ISIC C) +
+
diff --git a/ipi-survey-platform/src/pages/ManufacturingIndex/ManufacturingIndexPopup/ManufacturingTotal.jsx b/ipi-survey-platform/src/pages/ManufacturingIndex/ManufacturingIndexPopup/ManufacturingTotal.jsx index 28c3685..fd7bf15 100644 --- a/ipi-survey-platform/src/pages/ManufacturingIndex/ManufacturingIndexPopup/ManufacturingTotal.jsx +++ b/ipi-survey-platform/src/pages/ManufacturingIndex/ManufacturingIndexPopup/ManufacturingTotal.jsx @@ -97,6 +97,23 @@ const ManufacturingTotal = ({ selectedMonthData, setActiveTab, monthlyOverviewDa the same month last year.

+
+

Laspeyres reminder

+

Manufacturing indext = Σ (weightj × group_indexj,t)

+

+ Weights are fixed at base year (2022) and reflect each group's share in total manufacturing value added. +

+ +
+

Operational notes

+
    +
  • Manufacturing indices are auto-generated on the 11th using the latest validated item-level data for the previous reference month.
  • +
  • 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.
  • +
  • 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.
  • +
+
+
+